udata 10.4.2.dev35274__py2.py3-none-any.whl → 10.4.2.dev35386__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.
- udata/core/activity/__init__.py +1 -0
- udata/core/activity/api.py +10 -2
- udata/core/activity/models.py +28 -1
- udata/core/activity/tasks.py +19 -4
- udata/core/dataservices/activities.py +53 -0
- udata/core/dataservices/models.py +16 -20
- udata/core/dataset/activities.py +52 -5
- udata/core/dataset/models.py +43 -42
- udata/core/metrics/models.py +1 -0
- udata/core/organization/activities.py +3 -2
- udata/core/organization/models.py +31 -31
- udata/core/owned.py +1 -1
- udata/core/reuse/activities.py +6 -5
- udata/core/reuse/models.py +8 -16
- udata/core/user/activities.py +17 -1
- udata/core/user/models.py +39 -32
- udata/mongo/datetime_fields.py +1 -0
- udata/static/chunks/{11.8a2f7828175824bcd74b.js → 11.b6f741fcc366abfad9c4.js} +3 -3
- udata/static/chunks/{11.8a2f7828175824bcd74b.js.map → 11.b6f741fcc366abfad9c4.js.map} +1 -1
- udata/static/chunks/{13.39e106d56f794ebd06a0.js → 13.2d06442dd9a05d9777b5.js} +2 -2
- udata/static/chunks/{13.39e106d56f794ebd06a0.js.map → 13.2d06442dd9a05d9777b5.js.map} +1 -1
- udata/static/chunks/{17.70cbb4a91b002338007e.js → 17.e8e4caaad5cb0cc0bacc.js} +2 -2
- udata/static/chunks/{17.70cbb4a91b002338007e.js.map → 17.e8e4caaad5cb0cc0bacc.js.map} +1 -1
- udata/static/chunks/{19.df16abde17a42033a7f8.js → 19.f03a102365af4315f9db.js} +3 -3
- udata/static/chunks/{19.df16abde17a42033a7f8.js.map → 19.f03a102365af4315f9db.js.map} +1 -1
- udata/static/chunks/{5.5660483641193b7f8295.js → 5.0fa1408dae4e76b87b2e.js} +3 -3
- udata/static/chunks/{5.5660483641193b7f8295.js.map → 5.0fa1408dae4e76b87b2e.js.map} +1 -1
- udata/static/chunks/{6.30dce49d17db07600b06.js → 6.d663709d877baa44a71e.js} +3 -3
- udata/static/chunks/{6.30dce49d17db07600b06.js.map → 6.d663709d877baa44a71e.js.map} +1 -1
- udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.778091d55cd8ea39af6b.js} +2 -2
- udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.778091d55cd8ea39af6b.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_activities_api.py +29 -1
- {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35386.dist-info}/METADATA +4 -2
- {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35386.dist-info}/RECORD +40 -39
- {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35386.dist-info}/LICENSE +0 -0
- {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35386.dist-info}/WHEEL +0 -0
- {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35386.dist-info}/entry_points.txt +0 -0
- {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35386.dist-info}/top_level.txt +0 -0
udata/core/activity/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ log = logging.getLogger(__name__)
|
|
|
6
6
|
def init_app(app):
|
|
7
7
|
# Load all core actvitiess
|
|
8
8
|
import udata.core.user.activities # noqa
|
|
9
|
+
import udata.core.dataservices.activities # noqa
|
|
9
10
|
import udata.core.dataset.activities # noqa
|
|
10
11
|
import udata.core.reuse.activities # noqa
|
|
11
12
|
import udata.core.organization.activities # noqa
|
udata/core/activity/api.py
CHANGED
|
@@ -4,6 +4,7 @@ from bson import ObjectId
|
|
|
4
4
|
from mongoengine.errors import DoesNotExist
|
|
5
5
|
|
|
6
6
|
from udata.api import API, api, fields
|
|
7
|
+
from udata.auth import current_user
|
|
7
8
|
from udata.core.organization.api_fields import org_ref_fields
|
|
8
9
|
from udata.core.user.api_fields import user_ref_fields
|
|
9
10
|
from udata.models import Activity, db
|
|
@@ -46,6 +47,7 @@ activity_fields = api.model(
|
|
|
46
47
|
"label": fields.String(description="The label of the activity", required=True),
|
|
47
48
|
"key": fields.String(description="The key of the activity", required=True),
|
|
48
49
|
"icon": fields.String(description="The icon of the activity", required=True),
|
|
50
|
+
"changes": fields.List(fields.String, description="Changed attributes as list"),
|
|
49
51
|
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
50
52
|
},
|
|
51
53
|
)
|
|
@@ -76,7 +78,7 @@ class SiteActivityAPI(API):
|
|
|
76
78
|
@api.expect(activity_parser)
|
|
77
79
|
@api.marshal_with(activity_page_fields)
|
|
78
80
|
def get(self):
|
|
79
|
-
"""Fetch site activity, optionally filtered by user
|
|
81
|
+
"""Fetch site activity, optionally filtered by user or org."""
|
|
80
82
|
args = activity_parser.parse_args()
|
|
81
83
|
qs = Activity.objects
|
|
82
84
|
|
|
@@ -95,10 +97,11 @@ class SiteActivityAPI(API):
|
|
|
95
97
|
qs = qs.order_by("-created_at")
|
|
96
98
|
qs = qs.paginate(args["page"], args["page_size"])
|
|
97
99
|
|
|
98
|
-
# Filter out DBRefs
|
|
100
|
+
# - Filter out DBRefs
|
|
99
101
|
# Always return a result even not complete
|
|
100
102
|
# But log the error (ie. visible in sentry, silent for user)
|
|
101
103
|
# Can happen when someone manually delete an object in DB (ie. without proper purge)
|
|
104
|
+
# - Filter out private items (except for sysadmin users)
|
|
102
105
|
safe_items = []
|
|
103
106
|
for item in qs.queryset.items:
|
|
104
107
|
try:
|
|
@@ -106,6 +109,11 @@ class SiteActivityAPI(API):
|
|
|
106
109
|
except DoesNotExist as e:
|
|
107
110
|
log.error(e, exc_info=True)
|
|
108
111
|
else:
|
|
112
|
+
if hasattr(item.related_to, "private") and (
|
|
113
|
+
current_user.is_anonymous or not current_user.sysadmin
|
|
114
|
+
):
|
|
115
|
+
if item.related_to.private:
|
|
116
|
+
continue
|
|
109
117
|
safe_items.append(item)
|
|
110
118
|
qs.queryset.items = safe_items
|
|
111
119
|
|
udata/core/activity/models.py
CHANGED
|
@@ -3,6 +3,7 @@ from datetime import datetime
|
|
|
3
3
|
from blinker import Signal
|
|
4
4
|
from mongoengine.signals import post_save
|
|
5
5
|
|
|
6
|
+
from udata.api_fields import get_fields
|
|
6
7
|
from udata.auth import current_user
|
|
7
8
|
from udata.mongo import db
|
|
8
9
|
|
|
@@ -36,6 +37,7 @@ class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
|
|
|
36
37
|
organization = db.ReferenceField("Organization")
|
|
37
38
|
related_to = db.ReferenceField(db.DomainModel, required=True)
|
|
38
39
|
created_at = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
40
|
+
changes = db.ListField(db.StringField())
|
|
39
41
|
|
|
40
42
|
extras = db.ExtrasField()
|
|
41
43
|
|
|
@@ -65,11 +67,36 @@ class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
|
|
|
65
67
|
return cls.on_new.connect(func, sender=cls)
|
|
66
68
|
|
|
67
69
|
@classmethod
|
|
68
|
-
def emit(cls, related_to, organization=None, extras=None):
|
|
70
|
+
def emit(cls, related_to, organization=None, changed_fields=None, extras=None):
|
|
69
71
|
new_activity.send(
|
|
70
72
|
cls,
|
|
71
73
|
related_to=related_to,
|
|
72
74
|
actor=current_user._get_current_object(),
|
|
73
75
|
organization=organization,
|
|
76
|
+
changes=changed_fields,
|
|
74
77
|
extras=extras,
|
|
75
78
|
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Auditable(object):
|
|
82
|
+
@classmethod
|
|
83
|
+
def post_save(cls, sender, document, **kwargs):
|
|
84
|
+
try:
|
|
85
|
+
auditable_fields = [
|
|
86
|
+
key for key, field, info in get_fields(cls) if info.get("auditable", True)
|
|
87
|
+
]
|
|
88
|
+
except Exception:
|
|
89
|
+
# for backward compatibility, all fields are treated as auditable for classes not using field() function
|
|
90
|
+
auditable_fields = document._get_changed_fields()
|
|
91
|
+
changed_fields = [
|
|
92
|
+
field for field in document._get_changed_fields() if field in auditable_fields
|
|
93
|
+
]
|
|
94
|
+
if "post_save" in kwargs.get("ignores", []):
|
|
95
|
+
return
|
|
96
|
+
cls.after_save.send(document)
|
|
97
|
+
if kwargs.get("created"):
|
|
98
|
+
cls.on_create.send(document)
|
|
99
|
+
elif len(changed_fields):
|
|
100
|
+
cls.on_update.send(document, changed_fields=changed_fields)
|
|
101
|
+
if getattr(document, "deleted_at", None) or getattr(document, "deleted", None):
|
|
102
|
+
cls.on_delete.send(document)
|
udata/core/activity/tasks.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from collections.abc import Iterable
|
|
2
3
|
|
|
3
4
|
from udata.models import Organization, User, db
|
|
4
5
|
from udata.tasks import task
|
|
@@ -9,28 +10,36 @@ log = logging.getLogger(__name__)
|
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@new_activity.connect
|
|
12
|
-
def delay_activity(cls, related_to, actor, organization=None, extras=None):
|
|
13
|
+
def delay_activity(cls, related_to, actor, organization=None, changes=None, extras=None):
|
|
13
14
|
emit_activity.delay(
|
|
14
15
|
cls.__name__,
|
|
15
16
|
str(actor.id),
|
|
16
17
|
related_to_cls=related_to.__class__.__name__,
|
|
17
18
|
related_to_id=str(related_to.id),
|
|
18
19
|
organization_id=str(organization.id) if organization else None,
|
|
20
|
+
changes=changes,
|
|
19
21
|
extras=extras,
|
|
20
22
|
)
|
|
21
23
|
|
|
22
24
|
|
|
23
25
|
@task
|
|
24
26
|
def emit_activity(
|
|
25
|
-
classname,
|
|
27
|
+
classname,
|
|
28
|
+
actor_id,
|
|
29
|
+
related_to_cls,
|
|
30
|
+
related_to_id,
|
|
31
|
+
organization_id=None,
|
|
32
|
+
changes=None,
|
|
33
|
+
extras=None,
|
|
26
34
|
):
|
|
27
35
|
log.debug(
|
|
28
|
-
"Emit new activity: %s %s %s %s %s %s",
|
|
36
|
+
"Emit new activity: %s %s %s %s %s %s %s",
|
|
29
37
|
classname,
|
|
30
38
|
actor_id,
|
|
31
39
|
related_to_cls,
|
|
32
40
|
related_to_id,
|
|
33
41
|
organization_id,
|
|
42
|
+
", ".join(changes) if changes and isinstance(changes, Iterable) else "",
|
|
34
43
|
extras,
|
|
35
44
|
)
|
|
36
45
|
cls = db.resolve_model(classname)
|
|
@@ -40,4 +49,10 @@ def emit_activity(
|
|
|
40
49
|
organization = Organization.objects.get(pk=organization_id)
|
|
41
50
|
else:
|
|
42
51
|
organization = None
|
|
43
|
-
cls.objects.create(
|
|
52
|
+
cls.objects.create(
|
|
53
|
+
actor=actor,
|
|
54
|
+
related_to=related_to,
|
|
55
|
+
organization=organization,
|
|
56
|
+
changes=changes,
|
|
57
|
+
extras=extras,
|
|
58
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from udata.auth import current_user
|
|
2
|
+
from udata.i18n import lazy_gettext as _
|
|
3
|
+
from udata.models import Activity, Dataservice, db
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
|
+
"UserCreatedDataservice",
|
|
7
|
+
"UserUpdatedDataservice",
|
|
8
|
+
"UserDeletedDataservice",
|
|
9
|
+
"DataserviceRelatedActivity",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DataserviceRelatedActivity(object):
|
|
14
|
+
related_to = db.ReferenceField("Dataservice")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class UserCreatedDataservice(DataserviceRelatedActivity, Activity):
|
|
18
|
+
key = "dataservice:created"
|
|
19
|
+
icon = "fa fa-plus"
|
|
20
|
+
badge_type = "success"
|
|
21
|
+
label = _("created a dataservice")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UserUpdatedDataservice(DataserviceRelatedActivity, Activity):
|
|
25
|
+
key = "dataservice:updated"
|
|
26
|
+
icon = "fa fa-pencil"
|
|
27
|
+
label = _("updated a dataservice")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class UserDeletedDataservice(DataserviceRelatedActivity, Activity):
|
|
31
|
+
key = "dataservice:deleted"
|
|
32
|
+
icon = "fa fa-remove"
|
|
33
|
+
badge_type = "error"
|
|
34
|
+
label = _("deleted a dataservice")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@Dataservice.on_create.connect
|
|
38
|
+
def on_user_created_dataservice(dataservice):
|
|
39
|
+
if current_user and current_user.is_authenticated:
|
|
40
|
+
UserCreatedDataservice.emit(dataservice, dataservice.organization)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@Dataservice.on_update.connect
|
|
44
|
+
def on_user_updated_dataservice(dataservice, **kwargs):
|
|
45
|
+
changed_fields = kwargs.get("changed_fields", [])
|
|
46
|
+
if current_user and current_user.is_authenticated:
|
|
47
|
+
UserUpdatedDataservice.emit(dataservice, dataservice.organization, changed_fields)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@Dataservice.on_delete.connect
|
|
51
|
+
def on_user_deleted_dataservice(dataservice):
|
|
52
|
+
if current_user and current_user.is_authenticated:
|
|
53
|
+
UserDeletedDataservice.emit(dataservice, dataservice.organization)
|
|
@@ -8,6 +8,7 @@ from mongoengine.signals import post_save
|
|
|
8
8
|
import udata.core.contact_point.api_fields as contact_api_fields
|
|
9
9
|
import udata.core.dataset.api_fields as datasets_api_fields
|
|
10
10
|
from udata.api_fields import field, function_field, generate_fields
|
|
11
|
+
from udata.core.activity.models import Auditable
|
|
11
12
|
from udata.core.dataservices.constants import DATASERVICE_ACCESS_TYPES, DATASERVICE_FORMATS
|
|
12
13
|
from udata.core.dataset.models import Dataset
|
|
13
14
|
from udata.core.metrics.models import WithMetrics
|
|
@@ -105,7 +106,7 @@ class HarvestMetadata(db.EmbeddedDocument):
|
|
|
105
106
|
{"key": "views", "value": "metrics.views"},
|
|
106
107
|
],
|
|
107
108
|
)
|
|
108
|
-
class Dataservice(WithMetrics, Owned, db.Document):
|
|
109
|
+
class Dataservice(Auditable, WithMetrics, Owned, db.Document):
|
|
109
110
|
meta = {
|
|
110
111
|
"indexes": [
|
|
111
112
|
"$title",
|
|
@@ -117,6 +118,11 @@ class Dataservice(WithMetrics, Owned, db.Document):
|
|
|
117
118
|
"auto_create_index_on_save": True,
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
after_save = Signal()
|
|
122
|
+
on_create = Signal()
|
|
123
|
+
on_update = Signal()
|
|
124
|
+
on_delete = Signal()
|
|
125
|
+
|
|
120
126
|
verbose_name = _("dataservice")
|
|
121
127
|
|
|
122
128
|
def __str__(self):
|
|
@@ -175,7 +181,10 @@ class Dataservice(WithMetrics, Owned, db.Document):
|
|
|
175
181
|
description="Is the dataservice private to the owner or the organization",
|
|
176
182
|
)
|
|
177
183
|
|
|
178
|
-
extras = field(
|
|
184
|
+
extras = field(
|
|
185
|
+
db.ExtrasField(),
|
|
186
|
+
auditable=False,
|
|
187
|
+
)
|
|
179
188
|
|
|
180
189
|
contact_points = field(
|
|
181
190
|
db.ListField(
|
|
@@ -201,8 +210,9 @@ class Dataservice(WithMetrics, Owned, db.Document):
|
|
|
201
210
|
),
|
|
202
211
|
readonly=True,
|
|
203
212
|
sortable="last_modified",
|
|
213
|
+
auditable=False,
|
|
204
214
|
)
|
|
205
|
-
deleted_at = field(db.DateTimeField())
|
|
215
|
+
deleted_at = field(db.DateTimeField(), auditable=False)
|
|
206
216
|
archived_at = field(db.DateTimeField())
|
|
207
217
|
|
|
208
218
|
datasets = field(
|
|
@@ -223,6 +233,7 @@ class Dataservice(WithMetrics, Owned, db.Document):
|
|
|
223
233
|
harvest = field(
|
|
224
234
|
db.EmbeddedDocumentField(HarvestMetadata),
|
|
225
235
|
readonly=True,
|
|
236
|
+
auditable=False,
|
|
226
237
|
)
|
|
227
238
|
|
|
228
239
|
def url_for(self, *args, **kwargs):
|
|
@@ -250,26 +261,11 @@ class Dataservice(WithMetrics, Owned, db.Document):
|
|
|
250
261
|
|
|
251
262
|
def count_discussions(self):
|
|
252
263
|
self.metrics["discussions"] = Discussion.objects(subject=self, closed=None).count()
|
|
253
|
-
self.save()
|
|
264
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
254
265
|
|
|
255
266
|
def count_followers(self):
|
|
256
267
|
self.metrics["followers"] = Follow.objects(until=None).followers(self).count()
|
|
257
|
-
self.save()
|
|
258
|
-
|
|
259
|
-
on_create = Signal()
|
|
260
|
-
on_update = Signal()
|
|
261
|
-
on_delete = Signal()
|
|
262
|
-
|
|
263
|
-
@classmethod
|
|
264
|
-
def post_save(cls, sender, document, **kwargs):
|
|
265
|
-
if "post_save" in kwargs.get("ignores", []):
|
|
266
|
-
return
|
|
267
|
-
if kwargs.get("created"):
|
|
268
|
-
cls.on_create.send(document)
|
|
269
|
-
else:
|
|
270
|
-
cls.on_update.send(document)
|
|
271
|
-
if document.deleted_at:
|
|
272
|
-
cls.on_delete.send(document)
|
|
268
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
273
269
|
|
|
274
270
|
|
|
275
271
|
post_save.connect(Dataservice.post_save, sender=Dataservice)
|
udata/core/dataset/activities.py
CHANGED
|
@@ -35,19 +35,66 @@ class UserDeletedDataset(DatasetRelatedActivity, Activity):
|
|
|
35
35
|
label = _("deleted a dataset")
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
class UserAddedResourceToDataset(DatasetRelatedActivity, Activity):
|
|
39
|
+
key = "dataset:resource:added"
|
|
40
|
+
icon = "fa fa-plus"
|
|
41
|
+
label = _("added a resource to a dataset")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class UserUpdatedResource(DatasetRelatedActivity, Activity):
|
|
45
|
+
key = "dataset:resource:updated"
|
|
46
|
+
icon = "fa fa-pencil"
|
|
47
|
+
label = _("updated a resource")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class UserRemovedResourceFromDataset(DatasetRelatedActivity, Activity):
|
|
51
|
+
key = "dataset:resource:deleted"
|
|
52
|
+
icon = "fa fa-remove"
|
|
53
|
+
label = _("removed a resource from a dataset")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@Dataset.on_resource_added.connect
|
|
57
|
+
def on_user_added_resource_to_dataset(sender, document, **kwargs):
|
|
58
|
+
if current_user and current_user.is_authenticated:
|
|
59
|
+
UserAddedResourceToDataset.emit(
|
|
60
|
+
document, document.organization, None, {"resource_id": str(kwargs["resource_id"])}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@Dataset.on_resource_updated.connect
|
|
65
|
+
def on_user_updated_resource(sender, document, **kwargs):
|
|
66
|
+
changed_fields = kwargs.get("changed_fields", [])
|
|
67
|
+
if current_user and current_user.is_authenticated:
|
|
68
|
+
UserUpdatedResource.emit(
|
|
69
|
+
document,
|
|
70
|
+
document.organization,
|
|
71
|
+
changed_fields,
|
|
72
|
+
{"resource_id": str(kwargs["resource_id"])},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@Dataset.on_resource_removed.connect
|
|
77
|
+
def on_user_removed_resource_from_dataset(sender, document, **kwargs):
|
|
78
|
+
if current_user and current_user.is_authenticated:
|
|
79
|
+
UserRemovedResourceFromDataset.emit(
|
|
80
|
+
document, document.organization, None, {"resource_id": str(kwargs["resource_id"])}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
38
84
|
@Dataset.on_create.connect
|
|
39
85
|
def on_user_created_dataset(dataset):
|
|
40
|
-
if
|
|
86
|
+
if current_user and current_user.is_authenticated:
|
|
41
87
|
UserCreatedDataset.emit(dataset, dataset.organization)
|
|
42
88
|
|
|
43
89
|
|
|
44
90
|
@Dataset.on_update.connect
|
|
45
|
-
def on_user_updated_dataset(dataset):
|
|
46
|
-
|
|
47
|
-
|
|
91
|
+
def on_user_updated_dataset(dataset, **kwargs):
|
|
92
|
+
changed_fields = kwargs.get("changed_fields", [])
|
|
93
|
+
if current_user and current_user.is_authenticated:
|
|
94
|
+
UserUpdatedDataset.emit(dataset, dataset.organization, changed_fields)
|
|
48
95
|
|
|
49
96
|
|
|
50
97
|
@Dataset.on_delete.connect
|
|
51
98
|
def on_user_deleted_dataset(dataset):
|
|
52
|
-
if
|
|
99
|
+
if current_user and current_user.is_authenticated:
|
|
53
100
|
UserDeletedDataset.emit(dataset, dataset.organization)
|
udata/core/dataset/models.py
CHANGED
|
@@ -19,6 +19,7 @@ from werkzeug.utils import cached_property
|
|
|
19
19
|
from udata.api_fields import field
|
|
20
20
|
from udata.app import cache
|
|
21
21
|
from udata.core import storages
|
|
22
|
+
from udata.core.activity.models import Auditable
|
|
22
23
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
23
24
|
from udata.frontend.markdown import mdstrip
|
|
24
25
|
from udata.i18n import lazy_gettext as _
|
|
@@ -540,45 +541,57 @@ class DatasetBadgeMixin(BadgeMixin):
|
|
|
540
541
|
__badges__ = BADGES
|
|
541
542
|
|
|
542
543
|
|
|
543
|
-
class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
544
|
-
title = db.StringField(required=True)
|
|
545
|
-
acronym = db.StringField(max_length=128)
|
|
544
|
+
class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
545
|
+
title = field(db.StringField(required=True))
|
|
546
|
+
acronym = field(db.StringField(max_length=128))
|
|
546
547
|
# /!\ do not set directly the slug when creating or updating a dataset
|
|
547
548
|
# this will break the search indexation
|
|
548
|
-
slug =
|
|
549
|
-
|
|
549
|
+
slug = field(
|
|
550
|
+
db.SlugField(
|
|
551
|
+
max_length=255, required=True, populate_from="title", update=True, follow=True
|
|
552
|
+
),
|
|
553
|
+
auditable=False,
|
|
550
554
|
)
|
|
551
|
-
description = db.StringField(required=True, default="")
|
|
552
|
-
license = db.ReferenceField("License")
|
|
555
|
+
description = field(db.StringField(required=True, default=""))
|
|
556
|
+
license = field(db.ReferenceField("License"))
|
|
553
557
|
|
|
554
|
-
tags = db.TagListField()
|
|
555
|
-
resources = db.ListField(db.EmbeddedDocumentField(Resource))
|
|
558
|
+
tags = field(db.TagListField())
|
|
559
|
+
resources = field(db.ListField(db.EmbeddedDocumentField(Resource)), auditable=False)
|
|
556
560
|
|
|
557
|
-
private = db.BooleanField(default=False)
|
|
558
|
-
frequency = db.StringField(choices=list(UPDATE_FREQUENCIES.keys()))
|
|
559
|
-
frequency_date = db.DateTimeField(verbose_name=_("Future date of update"))
|
|
560
|
-
temporal_coverage = db.EmbeddedDocumentField(db.DateRange)
|
|
561
|
-
spatial = db.EmbeddedDocumentField(SpatialCoverage)
|
|
562
|
-
schema = db.EmbeddedDocumentField(Schema)
|
|
561
|
+
private = field(db.BooleanField(default=False))
|
|
562
|
+
frequency = field(db.StringField(choices=list(UPDATE_FREQUENCIES.keys())))
|
|
563
|
+
frequency_date = field(db.DateTimeField(verbose_name=_("Future date of update")))
|
|
564
|
+
temporal_coverage = field(db.EmbeddedDocumentField(db.DateRange))
|
|
565
|
+
spatial = field(db.EmbeddedDocumentField(SpatialCoverage))
|
|
566
|
+
schema = field(db.EmbeddedDocumentField(Schema))
|
|
563
567
|
|
|
564
|
-
ext = db.MapField(db.GenericEmbeddedDocumentField())
|
|
565
|
-
extras = db.ExtrasField()
|
|
566
|
-
harvest = db.EmbeddedDocumentField(HarvestDatasetMetadata)
|
|
568
|
+
ext = field(db.MapField(db.GenericEmbeddedDocumentField()), auditable=False)
|
|
569
|
+
extras = field(db.ExtrasField(), auditable=False)
|
|
570
|
+
harvest = field(db.EmbeddedDocumentField(HarvestDatasetMetadata), auditable=False)
|
|
567
571
|
|
|
568
|
-
quality_cached = db.DictField()
|
|
572
|
+
quality_cached = field(db.DictField(), auditable=False)
|
|
569
573
|
|
|
570
|
-
featured =
|
|
574
|
+
featured = field(
|
|
575
|
+
db.BooleanField(required=True, default=False),
|
|
576
|
+
auditable=False,
|
|
577
|
+
)
|
|
571
578
|
|
|
572
|
-
contact_points =
|
|
579
|
+
contact_points = field(
|
|
580
|
+
db.ListField(db.ReferenceField("ContactPoint", reverse_delete_rule=db.PULL))
|
|
581
|
+
)
|
|
573
582
|
|
|
574
|
-
created_at_internal =
|
|
575
|
-
verbose_name=_("Creation date"), default=datetime.utcnow, required=True
|
|
583
|
+
created_at_internal = field(
|
|
584
|
+
DateTimeField(verbose_name=_("Creation date"), default=datetime.utcnow, required=True),
|
|
585
|
+
auditable=False,
|
|
576
586
|
)
|
|
577
|
-
last_modified_internal =
|
|
578
|
-
|
|
587
|
+
last_modified_internal = field(
|
|
588
|
+
DateTimeField(
|
|
589
|
+
verbose_name=_("Last modification date"), default=datetime.utcnow, required=True
|
|
590
|
+
),
|
|
591
|
+
auditable=False,
|
|
579
592
|
)
|
|
580
|
-
deleted = db.DateTimeField()
|
|
581
|
-
archived = db.DateTimeField()
|
|
593
|
+
deleted = field(db.DateTimeField(), auditable=False)
|
|
594
|
+
archived = field(db.DateTimeField())
|
|
582
595
|
|
|
583
596
|
def __str__(self):
|
|
584
597
|
return self.title or ""
|
|
@@ -654,18 +667,6 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
|
654
667
|
def pre_save(cls, sender, document, **kwargs):
|
|
655
668
|
cls.before_save.send(document)
|
|
656
669
|
|
|
657
|
-
@classmethod
|
|
658
|
-
def post_save(cls, sender, document, **kwargs):
|
|
659
|
-
if "post_save" in kwargs.get("ignores", []):
|
|
660
|
-
return
|
|
661
|
-
cls.after_save.send(document)
|
|
662
|
-
if kwargs.get("created"):
|
|
663
|
-
cls.on_create.send(document)
|
|
664
|
-
else:
|
|
665
|
-
cls.on_update.send(document)
|
|
666
|
-
if document.deleted:
|
|
667
|
-
cls.on_delete.send(document)
|
|
668
|
-
|
|
669
670
|
def clean(self):
|
|
670
671
|
super(Dataset, self).clean()
|
|
671
672
|
if self.frequency in LEGACY_FREQUENCIES:
|
|
@@ -1046,19 +1047,19 @@ class Dataset(WithMetrics, DatasetBadgeMixin, Owned, db.Document):
|
|
|
1046
1047
|
from udata.models import Discussion
|
|
1047
1048
|
|
|
1048
1049
|
self.metrics["discussions"] = Discussion.objects(subject=self, closed=None).count()
|
|
1049
|
-
self.save()
|
|
1050
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
1050
1051
|
|
|
1051
1052
|
def count_reuses(self):
|
|
1052
1053
|
from udata.models import Reuse
|
|
1053
1054
|
|
|
1054
1055
|
self.metrics["reuses"] = Reuse.objects(datasets=self).visible().count()
|
|
1055
|
-
self.save()
|
|
1056
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
1056
1057
|
|
|
1057
1058
|
def count_followers(self):
|
|
1058
1059
|
from udata.models import Follow
|
|
1059
1060
|
|
|
1060
1061
|
self.metrics["followers"] = Follow.objects(until=None).followers(self).count()
|
|
1061
|
-
self.save()
|
|
1062
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
1062
1063
|
|
|
1063
1064
|
|
|
1064
1065
|
pre_init.connect(Dataset.pre_init, sender=Dataset)
|
udata/core/metrics/models.py
CHANGED
|
@@ -32,6 +32,7 @@ def on_user_created_organization(organization):
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
@Organization.on_update.connect
|
|
35
|
-
def on_user_updated_organization(organization):
|
|
35
|
+
def on_user_updated_organization(organization, **kwargs):
|
|
36
|
+
changed_fields = kwargs.get("changed_fields", [])
|
|
36
37
|
if current_user and current_user.is_authenticated:
|
|
37
|
-
UserUpdatedOrganization.emit(organization, organization)
|
|
38
|
+
UserUpdatedOrganization.emit(organization, organization, changed_fields)
|
|
@@ -6,6 +6,7 @@ from mongoengine.signals import post_save, pre_save
|
|
|
6
6
|
from werkzeug.utils import cached_property
|
|
7
7
|
|
|
8
8
|
from udata.api_fields import field
|
|
9
|
+
from udata.core.activity.models import Auditable
|
|
9
10
|
from udata.core.badges.models import Badge, BadgeMixin, BadgesList
|
|
10
11
|
from udata.core.metrics.models import WithMetrics
|
|
11
12
|
from udata.core.storages import avatars, default_image_basename
|
|
@@ -110,29 +111,35 @@ class OrganizationBadgeMixin(BadgeMixin):
|
|
|
110
111
|
__badges__ = BADGES
|
|
111
112
|
|
|
112
113
|
|
|
113
|
-
class Organization(WithMetrics, OrganizationBadgeMixin, db.Datetimed, db.Document):
|
|
114
|
-
name = db.StringField(required=True)
|
|
115
|
-
acronym = db.StringField(max_length=128)
|
|
116
|
-
slug =
|
|
117
|
-
max_length=255, required=True, populate_from="name", update=True, follow=True
|
|
114
|
+
class Organization(Auditable, WithMetrics, OrganizationBadgeMixin, db.Datetimed, db.Document):
|
|
115
|
+
name = field(db.StringField(required=True))
|
|
116
|
+
acronym = field(db.StringField(max_length=128))
|
|
117
|
+
slug = field(
|
|
118
|
+
db.SlugField(max_length=255, required=True, populate_from="name", update=True, follow=True),
|
|
119
|
+
auditable=False,
|
|
118
120
|
)
|
|
119
|
-
description = db.StringField(required=True)
|
|
120
|
-
url = db.URLField()
|
|
121
|
-
image_url = db.StringField()
|
|
122
|
-
logo =
|
|
123
|
-
|
|
121
|
+
description = field(db.StringField(required=True))
|
|
122
|
+
url = field(db.URLField())
|
|
123
|
+
image_url = field(db.StringField())
|
|
124
|
+
logo = field(
|
|
125
|
+
db.ImageField(
|
|
126
|
+
fs=avatars,
|
|
127
|
+
basename=default_image_basename,
|
|
128
|
+
max_size=LOGO_MAX_SIZE,
|
|
129
|
+
thumbnails=LOGO_SIZES,
|
|
130
|
+
)
|
|
124
131
|
)
|
|
125
|
-
business_number_id = db.StringField(max_length=ORG_BID_SIZE_LIMIT)
|
|
132
|
+
business_number_id = field(db.StringField(max_length=ORG_BID_SIZE_LIMIT))
|
|
126
133
|
|
|
127
|
-
members = db.ListField(db.EmbeddedDocumentField(Member))
|
|
128
|
-
teams = db.ListField(db.EmbeddedDocumentField(Team))
|
|
129
|
-
requests = db.ListField(db.EmbeddedDocumentField(MembershipRequest))
|
|
134
|
+
members = field(db.ListField(db.EmbeddedDocumentField(Member)))
|
|
135
|
+
teams = field(db.ListField(db.EmbeddedDocumentField(Team)))
|
|
136
|
+
requests = field(db.ListField(db.EmbeddedDocumentField(MembershipRequest)))
|
|
130
137
|
|
|
131
|
-
ext = db.MapField(db.GenericEmbeddedDocumentField())
|
|
132
|
-
zone = db.StringField()
|
|
133
|
-
extras = db.OrganizationExtrasField()
|
|
138
|
+
ext = field(db.MapField(db.GenericEmbeddedDocumentField()))
|
|
139
|
+
zone = field(db.StringField())
|
|
140
|
+
extras = field(db.OrganizationExtrasField(), auditable=False)
|
|
134
141
|
|
|
135
|
-
deleted = db.DateTimeField()
|
|
142
|
+
deleted = field(db.DateTimeField())
|
|
136
143
|
|
|
137
144
|
meta = {
|
|
138
145
|
"indexes": [
|
|
@@ -168,19 +175,12 @@ class Organization(WithMetrics, OrganizationBadgeMixin, db.Datetimed, db.Documen
|
|
|
168
175
|
on_update = Signal()
|
|
169
176
|
before_delete = Signal()
|
|
170
177
|
after_delete = Signal()
|
|
178
|
+
on_delete = Signal()
|
|
171
179
|
|
|
172
180
|
@classmethod
|
|
173
181
|
def pre_save(cls, sender, document, **kwargs):
|
|
174
182
|
cls.before_save.send(document)
|
|
175
183
|
|
|
176
|
-
@classmethod
|
|
177
|
-
def post_save(cls, sender, document, **kwargs):
|
|
178
|
-
cls.after_save.send(document)
|
|
179
|
-
if kwargs.get("created"):
|
|
180
|
-
cls.on_create.send(document)
|
|
181
|
-
else:
|
|
182
|
-
cls.on_update.send(document)
|
|
183
|
-
|
|
184
184
|
def url_for(self, *args, **kwargs):
|
|
185
185
|
return endpoint_for("organizations.show", "api.organization", org=self, *args, **kwargs)
|
|
186
186
|
|
|
@@ -296,31 +296,31 @@ class Organization(WithMetrics, OrganizationBadgeMixin, db.Datetimed, db.Documen
|
|
|
296
296
|
|
|
297
297
|
def count_members(self):
|
|
298
298
|
self.metrics["members"] = len(self.members)
|
|
299
|
-
self.save()
|
|
299
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
300
300
|
|
|
301
301
|
def count_datasets(self):
|
|
302
302
|
from udata.models import Dataset
|
|
303
303
|
|
|
304
304
|
self.metrics["datasets"] = Dataset.objects(organization=self).visible().count()
|
|
305
|
-
self.save()
|
|
305
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
306
306
|
|
|
307
307
|
def count_reuses(self):
|
|
308
308
|
from udata.models import Reuse
|
|
309
309
|
|
|
310
310
|
self.metrics["reuses"] = Reuse.objects(organization=self).visible().count()
|
|
311
|
-
self.save()
|
|
311
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
312
312
|
|
|
313
313
|
def count_dataservices(self):
|
|
314
314
|
from udata.models import Dataservice
|
|
315
315
|
|
|
316
316
|
self.metrics["dataservices"] = Dataservice.objects(organization=self).visible().count()
|
|
317
|
-
self.save()
|
|
317
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
318
318
|
|
|
319
319
|
def count_followers(self):
|
|
320
320
|
from udata.models import Follow
|
|
321
321
|
|
|
322
322
|
self.metrics["followers"] = Follow.objects(until=None).followers(self).count()
|
|
323
|
-
self.save()
|
|
323
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
324
324
|
|
|
325
325
|
|
|
326
326
|
pre_save.connect(Organization.pre_save, sender=Organization)
|
udata/core/owned.py
CHANGED
|
@@ -80,7 +80,7 @@ def check_organization_is_valid_for_current_user(organization, **_kwargs):
|
|
|
80
80
|
|
|
81
81
|
class Owned(object):
|
|
82
82
|
"""
|
|
83
|
-
A mixin to factorize owning
|
|
83
|
+
A mixin to factorize owning behavior between users and organizations.
|
|
84
84
|
"""
|
|
85
85
|
|
|
86
86
|
owner = field(
|