udata 10.9.1.dev0__py3-none-any.whl → 11.0.2.dev8__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/models.py +25 -3
- udata/core/dataservices/activities.py +5 -3
- udata/core/dataset/activities.py +8 -6
- udata/harvest/backends/base.py +16 -2
- udata/harvest/tests/test_actions.py +22 -1
- udata/settings.py +8 -0
- udata/static/chunks/{11.51d706fb9521c16976bc.js → 11.0f04e49a40a0a381bcce.js} +3 -3
- udata/static/chunks/{11.51d706fb9521c16976bc.js.map → 11.0f04e49a40a0a381bcce.js.map} +1 -1
- udata/static/chunks/{13.f29411b06be1883356a3.js → 13.39e106d56f794ebd06a0.js} +2 -2
- udata/static/chunks/{13.f29411b06be1883356a3.js.map → 13.39e106d56f794ebd06a0.js.map} +1 -1
- udata/static/chunks/{17.3bd0340930d4a314ce9c.js → 17.70cbb4a91b002338007e.js} +2 -2
- udata/static/chunks/{17.3bd0340930d4a314ce9c.js.map → 17.70cbb4a91b002338007e.js.map} +1 -1
- udata/static/chunks/{19.8da42e8359d72afc2618.js → 19.df16abde17a42033a7f8.js} +3 -3
- udata/static/chunks/{19.8da42e8359d72afc2618.js.map → 19.df16abde17a42033a7f8.js.map} +1 -1
- udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.0f42630e6d8ff782928e.js} +2 -2
- udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.0f42630e6d8ff782928e.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/test_activity.py +142 -1
- {udata-10.9.1.dev0.dist-info → udata-11.0.2.dev8.dist-info}/METADATA +2 -2
- {udata-10.9.1.dev0.dist-info → udata-11.0.2.dev8.dist-info}/RECORD +25 -25
- {udata-10.9.1.dev0.dist-info → udata-11.0.2.dev8.dist-info}/WHEEL +0 -0
- {udata-10.9.1.dev0.dist-info → udata-11.0.2.dev8.dist-info}/entry_points.txt +0 -0
- {udata-10.9.1.dev0.dist-info → udata-11.0.2.dev8.dist-info}/licenses/LICENSE +0 -0
- {udata-10.9.1.dev0.dist-info → udata-11.0.2.dev8.dist-info}/top_level.txt +0 -0
udata/core/activity/models.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
from datetime import datetime
|
|
2
3
|
|
|
3
4
|
from blinker import Signal
|
|
5
|
+
from flask import g
|
|
4
6
|
from mongoengine.errors import DoesNotExist
|
|
5
7
|
from mongoengine.signals import post_save
|
|
6
8
|
|
|
@@ -13,6 +15,8 @@ from .signals import new_activity
|
|
|
13
15
|
|
|
14
16
|
__all__ = ("Activity",)
|
|
15
17
|
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
_registered_activities = {}
|
|
18
22
|
|
|
@@ -70,10 +74,15 @@ class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
|
|
|
70
74
|
|
|
71
75
|
@classmethod
|
|
72
76
|
def emit(cls, related_to, organization=None, changed_fields=None, extras=None):
|
|
77
|
+
if hasattr(g, "harvest_activity_user"):
|
|
78
|
+
# We're in the context of a harvest action with a harvest activity user to use as actor
|
|
79
|
+
actor = g.harvest_activity_user
|
|
80
|
+
else:
|
|
81
|
+
actor = current_user._get_current_object()
|
|
73
82
|
new_activity.send(
|
|
74
83
|
cls,
|
|
75
84
|
related_to=related_to,
|
|
76
|
-
actor=
|
|
85
|
+
actor=actor,
|
|
77
86
|
organization=organization,
|
|
78
87
|
changes=changed_fields,
|
|
79
88
|
extras=extras,
|
|
@@ -110,7 +119,9 @@ class Auditable(object):
|
|
|
110
119
|
# for backward compatibility, all fields are treated as auditable for classes not using field() function
|
|
111
120
|
auditable_fields = document._get_changed_fields()
|
|
112
121
|
changed_fields = [
|
|
113
|
-
field
|
|
122
|
+
field
|
|
123
|
+
for field in document._get_changed_fields()
|
|
124
|
+
if field.split(".")[0] in auditable_fields
|
|
114
125
|
]
|
|
115
126
|
if "post_save" in kwargs.get("ignores", []):
|
|
116
127
|
return
|
|
@@ -119,6 +130,17 @@ class Auditable(object):
|
|
|
119
130
|
cls.on_create.send(document)
|
|
120
131
|
elif len(changed_fields):
|
|
121
132
|
previous = getattr(document, "_previous_changed_fields", None)
|
|
122
|
-
|
|
133
|
+
# make sure that changed fields have actually changed when comparing the document
|
|
134
|
+
# once it has been reloaded. It may have been cleaned or normalized when saved to mongo.
|
|
135
|
+
# We compare them one by one with the previous value stored in _previous_changed_fields.
|
|
136
|
+
# See https://github.com/opendatateam/udata/pull/3412 for more context.
|
|
137
|
+
document.reload()
|
|
138
|
+
changed_fields = [
|
|
139
|
+
field
|
|
140
|
+
for field in changed_fields
|
|
141
|
+
if previous[field] != get_field_value_from_path(document, field)
|
|
142
|
+
]
|
|
143
|
+
if changed_fields:
|
|
144
|
+
cls.on_update.send(document, changed_fields=changed_fields, previous=previous)
|
|
123
145
|
if getattr(document, "deleted_at", None) or getattr(document, "deleted", None):
|
|
124
146
|
cls.on_delete.send(document)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from flask import g
|
|
2
|
+
|
|
1
3
|
from udata.auth import current_user
|
|
2
4
|
from udata.i18n import lazy_gettext as _
|
|
3
5
|
from udata.models import Activity, Dataservice, db
|
|
@@ -36,18 +38,18 @@ class UserDeletedDataservice(DataserviceRelatedActivity, Activity):
|
|
|
36
38
|
|
|
37
39
|
@Dataservice.on_create.connect
|
|
38
40
|
def on_user_created_dataservice(dataservice):
|
|
39
|
-
if current_user and current_user.is_authenticated:
|
|
41
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
40
42
|
UserCreatedDataservice.emit(dataservice, dataservice.organization)
|
|
41
43
|
|
|
42
44
|
|
|
43
45
|
@Dataservice.on_update.connect
|
|
44
46
|
def on_user_updated_dataservice(dataservice, **kwargs):
|
|
45
47
|
changed_fields = kwargs.get("changed_fields", [])
|
|
46
|
-
if current_user and current_user.is_authenticated:
|
|
48
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
47
49
|
UserUpdatedDataservice.emit(dataservice, dataservice.organization, changed_fields)
|
|
48
50
|
|
|
49
51
|
|
|
50
52
|
@Dataservice.on_delete.connect
|
|
51
53
|
def on_user_deleted_dataservice(dataservice):
|
|
52
|
-
if current_user and current_user.is_authenticated:
|
|
54
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
53
55
|
UserDeletedDataservice.emit(dataservice, dataservice.organization)
|
udata/core/dataset/activities.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from flask import g
|
|
2
|
+
|
|
1
3
|
from udata.auth import current_user
|
|
2
4
|
from udata.i18n import lazy_gettext as _
|
|
3
5
|
from udata.models import Activity, Dataset, db
|
|
@@ -55,7 +57,7 @@ class UserRemovedResourceFromDataset(DatasetRelatedActivity, Activity):
|
|
|
55
57
|
|
|
56
58
|
@Dataset.on_resource_added.connect
|
|
57
59
|
def on_user_added_resource_to_dataset(sender, document, **kwargs):
|
|
58
|
-
if current_user and current_user.is_authenticated:
|
|
60
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
59
61
|
UserAddedResourceToDataset.emit(
|
|
60
62
|
document, document.organization, None, {"resource_id": str(kwargs["resource_id"])}
|
|
61
63
|
)
|
|
@@ -64,7 +66,7 @@ def on_user_added_resource_to_dataset(sender, document, **kwargs):
|
|
|
64
66
|
@Dataset.on_resource_updated.connect
|
|
65
67
|
def on_user_updated_resource(sender, document, **kwargs):
|
|
66
68
|
changed_fields = kwargs.get("changed_fields", [])
|
|
67
|
-
if current_user and current_user.is_authenticated:
|
|
69
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
68
70
|
UserUpdatedResource.emit(
|
|
69
71
|
document,
|
|
70
72
|
document.organization,
|
|
@@ -75,7 +77,7 @@ def on_user_updated_resource(sender, document, **kwargs):
|
|
|
75
77
|
|
|
76
78
|
@Dataset.on_resource_removed.connect
|
|
77
79
|
def on_user_removed_resource_from_dataset(sender, document, **kwargs):
|
|
78
|
-
if current_user and current_user.is_authenticated:
|
|
80
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
79
81
|
UserRemovedResourceFromDataset.emit(
|
|
80
82
|
document, document.organization, None, {"resource_id": str(kwargs["resource_id"])}
|
|
81
83
|
)
|
|
@@ -83,18 +85,18 @@ def on_user_removed_resource_from_dataset(sender, document, **kwargs):
|
|
|
83
85
|
|
|
84
86
|
@Dataset.on_create.connect
|
|
85
87
|
def on_user_created_dataset(dataset):
|
|
86
|
-
if current_user and current_user.is_authenticated:
|
|
88
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
87
89
|
UserCreatedDataset.emit(dataset, dataset.organization)
|
|
88
90
|
|
|
89
91
|
|
|
90
92
|
@Dataset.on_update.connect
|
|
91
93
|
def on_user_updated_dataset(dataset, **kwargs):
|
|
92
94
|
changed_fields = kwargs.get("changed_fields", [])
|
|
93
|
-
if current_user and current_user.is_authenticated:
|
|
95
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
94
96
|
UserUpdatedDataset.emit(dataset, dataset.organization, changed_fields)
|
|
95
97
|
|
|
96
98
|
|
|
97
99
|
@Dataset.on_delete.connect
|
|
98
100
|
def on_user_deleted_dataset(dataset):
|
|
99
|
-
if current_user and current_user.is_authenticated:
|
|
101
|
+
if (current_user and current_user.is_authenticated) or hasattr(g, "harvest_activity_user"):
|
|
100
102
|
UserDeletedDataset.emit(dataset, dataset.organization)
|
udata/harvest/backends/base.py
CHANGED
|
@@ -4,14 +4,14 @@ from datetime import date, datetime, timedelta
|
|
|
4
4
|
from uuid import UUID
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
|
-
from flask import current_app
|
|
7
|
+
from flask import current_app, g
|
|
8
8
|
from voluptuous import MultipleInvalid, RequiredFieldInvalid
|
|
9
9
|
|
|
10
10
|
import udata.uris as uris
|
|
11
11
|
from udata.core.dataservices.models import Dataservice
|
|
12
12
|
from udata.core.dataservices.models import HarvestMetadata as HarvestDataserviceMetadata
|
|
13
13
|
from udata.core.dataset.models import HarvestDatasetMetadata
|
|
14
|
-
from udata.models import Dataset
|
|
14
|
+
from udata.models import Dataset, User
|
|
15
15
|
from udata.utils import safe_unicode
|
|
16
16
|
|
|
17
17
|
from ..exceptions import HarvestException, HarvestSkipException, HarvestValidationError
|
|
@@ -168,6 +168,17 @@ class BaseBackend(object):
|
|
|
168
168
|
self.job = factory(status="initialized", started=datetime.utcnow(), source=self.source)
|
|
169
169
|
|
|
170
170
|
before_harvest_job.send(self)
|
|
171
|
+
# Set harvest_activity_user on global context during the run
|
|
172
|
+
if current_app.config["HARVEST_ACTIVITY_USER_ID"]:
|
|
173
|
+
try:
|
|
174
|
+
# Try to fetch the existing harvest activity user
|
|
175
|
+
g.harvest_activity_user = User.objects.get(
|
|
176
|
+
id=current_app.config["HARVEST_ACTIVITY_USER_ID"]
|
|
177
|
+
)
|
|
178
|
+
except User.DoesNotExist:
|
|
179
|
+
log.exception(
|
|
180
|
+
"HARVEST_ACTIVITY_USER_ID does not seem to match an existing user id."
|
|
181
|
+
)
|
|
171
182
|
|
|
172
183
|
try:
|
|
173
184
|
self.inner_harvest()
|
|
@@ -199,6 +210,9 @@ class BaseBackend(object):
|
|
|
199
210
|
self.job.errors.append(error)
|
|
200
211
|
finally:
|
|
201
212
|
self.end_job()
|
|
213
|
+
# Clean harvest_activity_user on global context
|
|
214
|
+
if hasattr(g, "harvest_activity_user"):
|
|
215
|
+
delattr(g, "harvest_activity_user")
|
|
202
216
|
|
|
203
217
|
return self.job
|
|
204
218
|
|
|
@@ -4,16 +4,19 @@ from datetime import datetime, timedelta
|
|
|
4
4
|
from tempfile import NamedTemporaryFile
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
|
+
from flask import current_app
|
|
7
8
|
from mock import patch
|
|
8
9
|
|
|
10
|
+
from udata.core.activity.models import new_activity
|
|
9
11
|
from udata.core.dataservices.factories import DataserviceFactory
|
|
10
12
|
from udata.core.dataservices.models import HarvestMetadata as HarvestDataserviceMetadata
|
|
13
|
+
from udata.core.dataset.activities import UserCreatedDataset
|
|
11
14
|
from udata.core.dataset.factories import DatasetFactory
|
|
12
15
|
from udata.core.dataset.models import HarvestDatasetMetadata
|
|
13
16
|
from udata.core.organization.factories import OrganizationFactory
|
|
14
17
|
from udata.core.user.factories import UserFactory
|
|
15
18
|
from udata.models import Dataset, PeriodicTask
|
|
16
|
-
from udata.tests.helpers import assert_emit, assert_equal_dates
|
|
19
|
+
from udata.tests.helpers import assert_emit, assert_equal_dates, assert_not_emit
|
|
17
20
|
from udata.utils import faker
|
|
18
21
|
|
|
19
22
|
from .. import actions, signals
|
|
@@ -578,6 +581,24 @@ class ExecutionTestMixin(MockBackendsMixin):
|
|
|
578
581
|
self.action(source)
|
|
579
582
|
assert len(Dataset.objects) == 5
|
|
580
583
|
|
|
584
|
+
@pytest.mark.options(HARVEST_ACTIVITY_USER_ID="68b860182728e27218dd7c72")
|
|
585
|
+
def test_harvest_emit_activity(self):
|
|
586
|
+
# We need to init dataset activities module
|
|
587
|
+
import udata.core.dataset.activities # noqa
|
|
588
|
+
|
|
589
|
+
user = UserFactory(id=current_app.config["HARVEST_ACTIVITY_USER_ID"])
|
|
590
|
+
source = HarvestSourceFactory(backend="factory")
|
|
591
|
+
with assert_emit(Dataset.on_create, new_activity):
|
|
592
|
+
self.action(source)
|
|
593
|
+
|
|
594
|
+
# We have an activity for each dataset created by the source action
|
|
595
|
+
activities = UserCreatedDataset.objects(actor=user)
|
|
596
|
+
assert activities.count() == Dataset.objects().count()
|
|
597
|
+
|
|
598
|
+
# On a second run, we don't expect any signal sent (no creation, update or deletion)
|
|
599
|
+
with assert_not_emit(Dataset.on_create, Dataset.on_update, new_activity):
|
|
600
|
+
self.action(source)
|
|
601
|
+
|
|
581
602
|
|
|
582
603
|
class HarvestLaunchTest(ExecutionTestMixin):
|
|
583
604
|
def action(self, *args, **kwargs):
|
udata/settings.py
CHANGED
|
@@ -261,6 +261,8 @@ class Defaults(object):
|
|
|
261
261
|
|
|
262
262
|
DELAY_BEFORE_REMINDER_NOTIFICATION = 30 # Days
|
|
263
263
|
|
|
264
|
+
# Harvest settings
|
|
265
|
+
###########################################################################
|
|
264
266
|
HARVEST_ENABLE_MANUAL_RUN = False
|
|
265
267
|
|
|
266
268
|
HARVEST_PREVIEW_MAX_ITEMS = 20
|
|
@@ -285,7 +287,12 @@ class Defaults(object):
|
|
|
285
287
|
|
|
286
288
|
HARVEST_ISO19139_XSLT_URL = "https://raw.githubusercontent.com/SEMICeu/iso-19139-to-dcat-ap/refs/heads/geodcat-ap-2.0.0/iso-19139-to-dcat-ap.xsl"
|
|
287
289
|
|
|
290
|
+
# If set, harvest emit activities associated with this user as actor
|
|
291
|
+
# It should be a dedicated service account
|
|
292
|
+
HARVEST_ACTIVITY_USER_ID = None
|
|
293
|
+
|
|
288
294
|
# S3 connection details
|
|
295
|
+
###########################################################################
|
|
289
296
|
S3_URL = None
|
|
290
297
|
S3_ACCESS_KEY_ID = None
|
|
291
298
|
S3_SECRET_ACCESS_KEY = None
|
|
@@ -626,6 +633,7 @@ class Testing(object):
|
|
|
626
633
|
"check_deliverability": False
|
|
627
634
|
} # Disables deliverability for email domain name
|
|
628
635
|
PUBLISH_ON_RESOURCE_EVENTS = False
|
|
636
|
+
HARVEST_ACTIVITY_USER_ID = None
|
|
629
637
|
|
|
630
638
|
|
|
631
639
|
class Debug(Defaults):
|