udata 10.4.2.dev35266__py2.py3-none-any.whl → 10.4.2.dev35319__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 +1 -0
- 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 +49 -2
- udata/core/dataset/models.py +43 -42
- udata/core/metrics/models.py +1 -0
- udata/core/organization/activities.py +3 -2
- udata/core/organization/api_fields.py +6 -5
- udata/core/organization/models.py +31 -31
- udata/core/owned.py +1 -1
- udata/core/reuse/activities.py +3 -2
- udata/core/reuse/models.py +8 -16
- udata/core/user/activities.py +17 -1
- udata/core/user/api.py +1 -0
- udata/core/user/api_fields.py +6 -1
- udata/core/user/models.py +39 -32
- udata/mongo/datetime_fields.py +1 -0
- udata/tests/api/test_organizations_api.py +2 -2
- {udata-10.4.2.dev35266.dist-info → udata-10.4.2.dev35319.dist-info}/METADATA +4 -2
- {udata-10.4.2.dev35266.dist-info → udata-10.4.2.dev35319.dist-info}/RECORD +27 -26
- {udata-10.4.2.dev35266.dist-info → udata-10.4.2.dev35319.dist-info}/LICENSE +0 -0
- {udata-10.4.2.dev35266.dist-info → udata-10.4.2.dev35319.dist-info}/WHEEL +0 -0
- {udata-10.4.2.dev35266.dist-info → udata-10.4.2.dev35319.dist-info}/entry_points.txt +0 -0
- {udata-10.4.2.dev35266.dist-info → udata-10.4.2.dev35319.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
|
@@ -46,6 +46,7 @@ activity_fields = api.model(
|
|
|
46
46
|
"label": fields.String(description="The label of the activity", required=True),
|
|
47
47
|
"key": fields.String(description="The key of the activity", required=True),
|
|
48
48
|
"icon": fields.String(description="The icon of the activity", required=True),
|
|
49
|
+
"changes": fields.List(fields.String, description="Changed attributes as list"),
|
|
49
50
|
"extras": fields.Raw(description="Extras attributes as key-value pairs"),
|
|
50
51
|
},
|
|
51
52
|
)
|
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 not dataservice.private and 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 not dataservice.private and 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 not dataservice.private and 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,6 +35,52 @@ 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 not document.private and 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 not document.private and 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 not document.private and 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
86
|
if not dataset.private and current_user and current_user.is_authenticated:
|
|
@@ -42,9 +88,10 @@ def on_user_created_dataset(dataset):
|
|
|
42
88
|
|
|
43
89
|
|
|
44
90
|
@Dataset.on_update.connect
|
|
45
|
-
def on_user_updated_dataset(dataset):
|
|
91
|
+
def on_user_updated_dataset(dataset, **kwargs):
|
|
92
|
+
changed_fields = kwargs.get("changed_fields", [])
|
|
46
93
|
if not dataset.private and current_user and current_user.is_authenticated:
|
|
47
|
-
UserUpdatedDataset.emit(dataset, dataset.organization)
|
|
94
|
+
UserUpdatedDataset.emit(dataset, dataset.organization, changed_fields)
|
|
48
95
|
|
|
49
96
|
|
|
50
97
|
@Dataset.on_delete.connect
|
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)
|
|
@@ -42,9 +42,6 @@ org_ref_fields = api.inherit(
|
|
|
42
42
|
},
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
# This import is not at the top of the file to avoid circular imports
|
|
46
|
-
from udata.core.user.api_fields import user_ref_fields # noqa
|
|
47
|
-
|
|
48
45
|
|
|
49
46
|
def check_can_access_user_private_info():
|
|
50
47
|
# This endpoint is secure, only organization member has access.
|
|
@@ -64,14 +61,18 @@ def check_can_access_user_private_info():
|
|
|
64
61
|
def member_email_with_visibility_check(email):
|
|
65
62
|
if current_user_is_admin_or_self():
|
|
66
63
|
return email
|
|
64
|
+
name, domain = email.split("@")
|
|
67
65
|
if check_can_access_user_private_info():
|
|
68
66
|
# Obfuscate email partially for other members
|
|
69
|
-
name, domain = email.split("@")
|
|
70
67
|
name = name[:2] + "*" * (len(name) - 2)
|
|
71
68
|
return f"{name}@{domain}"
|
|
72
|
-
|
|
69
|
+
# Return only domain for other users
|
|
70
|
+
return f"***@{domain}"
|
|
73
71
|
|
|
74
72
|
|
|
73
|
+
# This import is not at the top of the file to avoid circular imports
|
|
74
|
+
from udata.core.user.api_fields import user_ref_fields # noqa
|
|
75
|
+
|
|
75
76
|
member_user_with_email_fields = api.inherit(
|
|
76
77
|
"MemberUserWithEmail",
|
|
77
78
|
user_ref_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(
|
udata/core/reuse/activities.py
CHANGED
|
@@ -38,9 +38,10 @@ def on_user_created_reuse(reuse):
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
@Reuse.on_update.connect
|
|
41
|
-
def on_user_updated_reuse(reuse):
|
|
41
|
+
def on_user_updated_reuse(reuse, **kwargs):
|
|
42
|
+
changed_fields = kwargs.get("changed_fields", [])
|
|
42
43
|
if not reuse.private and current_user and current_user.is_authenticated:
|
|
43
|
-
UserUpdatedReuse.emit(reuse, reuse.organization)
|
|
44
|
+
UserUpdatedReuse.emit(reuse, reuse.organization, changed_fields)
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
@Reuse.on_delete.connect
|
udata/core/reuse/models.py
CHANGED
|
@@ -3,6 +3,7 @@ from mongoengine.signals import post_save, pre_save
|
|
|
3
3
|
from werkzeug.utils import cached_property
|
|
4
4
|
|
|
5
5
|
from udata.api_fields import field, function_field, generate_fields
|
|
6
|
+
from udata.core.activity.models import Auditable
|
|
6
7
|
from udata.core.dataset.api_fields import dataset_fields
|
|
7
8
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
8
9
|
from udata.core.reuse.api_fields import BIGGEST_IMAGE_SIZE
|
|
@@ -60,7 +61,7 @@ class ReuseBadgeMixin(BadgeMixin):
|
|
|
60
61
|
additional_filters={"organization_badge": "organization.badges"},
|
|
61
62
|
mask="*,datasets{id,title,uri,page}",
|
|
62
63
|
)
|
|
63
|
-
class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
64
|
+
class Reuse(db.Datetimed, Auditable, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
64
65
|
title = field(
|
|
65
66
|
db.StringField(required=True),
|
|
66
67
|
sortable=True,
|
|
@@ -71,6 +72,7 @@ class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
|
71
72
|
max_length=255, required=True, populate_from="title", update=True, follow=True
|
|
72
73
|
),
|
|
73
74
|
readonly=True,
|
|
75
|
+
auditable=False,
|
|
74
76
|
)
|
|
75
77
|
description = field(
|
|
76
78
|
db.StringField(required=True),
|
|
@@ -126,15 +128,17 @@ class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
|
126
128
|
private = field(db.BooleanField(default=False), filterable={})
|
|
127
129
|
|
|
128
130
|
ext = db.MapField(db.GenericEmbeddedDocumentField())
|
|
129
|
-
extras = field(db.ExtrasField())
|
|
131
|
+
extras = field(db.ExtrasField(), auditable=False)
|
|
130
132
|
|
|
131
133
|
featured = field(
|
|
132
134
|
db.BooleanField(),
|
|
133
135
|
filterable={},
|
|
134
136
|
readonly=True,
|
|
137
|
+
auditable=False,
|
|
135
138
|
)
|
|
136
139
|
deleted = field(
|
|
137
140
|
db.DateTimeField(),
|
|
141
|
+
auditable=False,
|
|
138
142
|
)
|
|
139
143
|
archived = field(
|
|
140
144
|
db.DateTimeField(),
|
|
@@ -181,18 +185,6 @@ class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
|
181
185
|
# Emit before_save
|
|
182
186
|
cls.before_save.send(document)
|
|
183
187
|
|
|
184
|
-
@classmethod
|
|
185
|
-
def post_save(cls, sender, document, **kwargs):
|
|
186
|
-
if "post_save" in kwargs.get("ignores", []):
|
|
187
|
-
return
|
|
188
|
-
cls.after_save.send(document)
|
|
189
|
-
if kwargs.get("created"):
|
|
190
|
-
cls.on_create.send(document)
|
|
191
|
-
else:
|
|
192
|
-
cls.on_update.send(document)
|
|
193
|
-
if document.deleted:
|
|
194
|
-
cls.on_delete.send(document)
|
|
195
|
-
|
|
196
188
|
def url_for(self, *args, **kwargs):
|
|
197
189
|
return endpoint_for("reuses.show", "api.reuse", reuse=self, *args, **kwargs)
|
|
198
190
|
|
|
@@ -289,13 +281,13 @@ class Reuse(db.Datetimed, WithMetrics, ReuseBadgeMixin, Owned, db.Document):
|
|
|
289
281
|
from udata.models import Discussion
|
|
290
282
|
|
|
291
283
|
self.metrics["discussions"] = Discussion.objects(subject=self, closed=None).count()
|
|
292
|
-
self.save()
|
|
284
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
293
285
|
|
|
294
286
|
def count_followers(self):
|
|
295
287
|
from udata.models import Follow
|
|
296
288
|
|
|
297
289
|
self.metrics["followers"] = Follow.objects(until=None).followers(self).count()
|
|
298
|
-
self.save()
|
|
290
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
299
291
|
|
|
300
292
|
|
|
301
293
|
pre_save.connect(Reuse.pre_save, sender=Reuse)
|
udata/core/user/activities.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from flask_security import current_user
|
|
2
2
|
|
|
3
|
+
from udata.core.dataservices.activities import DataserviceRelatedActivity
|
|
4
|
+
from udata.core.dataservices.models import Dataservice
|
|
3
5
|
from udata.core.dataset.activities import DatasetRelatedActivity
|
|
4
6
|
from udata.core.discussions.signals import on_new_discussion, on_new_discussion_comment
|
|
5
7
|
from udata.core.followers.signals import on_follow
|
|
@@ -40,6 +42,11 @@ class UserFollowedUser(FollowActivity, Activity):
|
|
|
40
42
|
template = "activity/user.html"
|
|
41
43
|
|
|
42
44
|
|
|
45
|
+
class UserDiscussedDataservice(DiscussActivity, DataserviceRelatedActivity, Activity):
|
|
46
|
+
key = "dataservice:discussed"
|
|
47
|
+
label = _("discussed a dataservice")
|
|
48
|
+
|
|
49
|
+
|
|
43
50
|
class UserDiscussedDataset(DiscussActivity, DatasetRelatedActivity, Activity):
|
|
44
51
|
key = "dataset:discussed"
|
|
45
52
|
label = _("discussed a dataset")
|
|
@@ -50,6 +57,11 @@ class UserDiscussedReuse(DiscussActivity, ReuseRelatedActivity, Activity):
|
|
|
50
57
|
label = _("discussed a reuse")
|
|
51
58
|
|
|
52
59
|
|
|
60
|
+
class UserFollowedDataservice(FollowActivity, DataserviceRelatedActivity, Activity):
|
|
61
|
+
key = "dataservice:followed"
|
|
62
|
+
label = _("followed a dataservice")
|
|
63
|
+
|
|
64
|
+
|
|
53
65
|
class UserFollowedDataset(FollowActivity, DatasetRelatedActivity, Activity):
|
|
54
66
|
key = "dataset:followed"
|
|
55
67
|
label = _("followed a dataset")
|
|
@@ -68,7 +80,9 @@ class UserFollowedOrganization(FollowActivity, OrgRelatedActivity, Activity):
|
|
|
68
80
|
@on_follow.connect
|
|
69
81
|
def write_activity_on_follow(follow, **kwargs):
|
|
70
82
|
if current_user.is_authenticated:
|
|
71
|
-
if isinstance(follow.following,
|
|
83
|
+
if isinstance(follow.following, Dataservice):
|
|
84
|
+
UserFollowedDataservice.emit(follow.following)
|
|
85
|
+
elif isinstance(follow.following, Dataset):
|
|
72
86
|
UserFollowedDataset.emit(follow.following)
|
|
73
87
|
elif isinstance(follow.following, Reuse):
|
|
74
88
|
UserFollowedReuse.emit(follow.following)
|
|
@@ -82,6 +96,8 @@ def write_activity_on_follow(follow, **kwargs):
|
|
|
82
96
|
@on_new_discussion_comment.connect
|
|
83
97
|
def write_activity_on_discuss(discussion, **kwargs):
|
|
84
98
|
if current_user.is_authenticated:
|
|
99
|
+
if isinstance(discussion.subject, Dataservice):
|
|
100
|
+
UserDiscussedDataservice.emit(discussion.subject)
|
|
85
101
|
if isinstance(discussion.subject, Dataset):
|
|
86
102
|
UserDiscussedDataset.emit(discussion.subject)
|
|
87
103
|
elif isinstance(discussion.subject, Reuse):
|
udata/core/user/api.py
CHANGED
udata/core/user/api_fields.py
CHANGED
|
@@ -30,7 +30,7 @@ user_ref_fields = api.inherit(
|
|
|
30
30
|
},
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
-
from udata.core.organization.api_fields import org_ref_fields # noqa
|
|
33
|
+
from udata.core.organization.api_fields import member_email_with_visibility_check, org_ref_fields # noqa
|
|
34
34
|
|
|
35
35
|
user_fields = api.model(
|
|
36
36
|
"User",
|
|
@@ -126,6 +126,11 @@ user_suggestion_fields = api.model(
|
|
|
126
126
|
"avatar_url": fields.ImageField(
|
|
127
127
|
size=BIGGEST_AVATAR_SIZE, description="The user avatar URL", readonly=True
|
|
128
128
|
),
|
|
129
|
+
"email": fields.Raw(
|
|
130
|
+
attribute=lambda o: member_email_with_visibility_check(o["email"]),
|
|
131
|
+
description="The user email (only the domain for non-admin user)",
|
|
132
|
+
readonly=True,
|
|
133
|
+
),
|
|
129
134
|
"slug": fields.String(description="The user permalink string", readonly=True),
|
|
130
135
|
},
|
|
131
136
|
)
|
udata/core/user/models.py
CHANGED
|
@@ -12,6 +12,7 @@ from mongoengine.signals import post_save, pre_save
|
|
|
12
12
|
from werkzeug.utils import cached_property
|
|
13
13
|
|
|
14
14
|
from udata import mail
|
|
15
|
+
from udata.api_fields import field
|
|
15
16
|
from udata.core import storages
|
|
16
17
|
from udata.core.discussions.models import Discussion
|
|
17
18
|
from udata.core.storages import avatars, default_image_basename
|
|
@@ -42,49 +43,53 @@ class UserSettings(db.EmbeddedDocument):
|
|
|
42
43
|
|
|
43
44
|
|
|
44
45
|
class User(WithMetrics, UserMixin, db.Document):
|
|
45
|
-
slug =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
slug = field(
|
|
47
|
+
db.SlugField(max_length=255, required=True, populate_from="fullname"), auditable=False
|
|
48
|
+
)
|
|
49
|
+
email = field(db.StringField(max_length=255, required=True, unique=True))
|
|
50
|
+
password = field(db.StringField())
|
|
51
|
+
active = field(db.BooleanField())
|
|
52
|
+
fs_uniquifier = field(db.StringField(max_length=64, unique=True, sparse=True))
|
|
53
|
+
roles = field(db.ListField(db.ReferenceField(Role), default=[]))
|
|
51
54
|
|
|
52
|
-
first_name = db.StringField(max_length=255, required=True)
|
|
53
|
-
last_name = db.StringField(max_length=255, required=True)
|
|
55
|
+
first_name = field(db.StringField(max_length=255, required=True))
|
|
56
|
+
last_name = field(db.StringField(max_length=255, required=True))
|
|
54
57
|
|
|
55
|
-
avatar_url = db.URLField()
|
|
56
|
-
avatar =
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
avatar_url = field(db.URLField())
|
|
59
|
+
avatar = field(
|
|
60
|
+
db.ImageField(fs=avatars, basename=default_image_basename, thumbnails=AVATAR_SIZES)
|
|
61
|
+
)
|
|
62
|
+
website = field(db.URLField())
|
|
63
|
+
about = field(db.StringField())
|
|
59
64
|
|
|
60
|
-
prefered_language = db.StringField()
|
|
65
|
+
prefered_language = field(db.StringField())
|
|
61
66
|
|
|
62
|
-
apikey = db.StringField()
|
|
67
|
+
apikey = field(db.StringField())
|
|
63
68
|
|
|
64
|
-
created_at = db.DateTimeField(default=datetime.utcnow, required=True)
|
|
69
|
+
created_at = field(db.DateTimeField(default=datetime.utcnow, required=True), auditable=False)
|
|
65
70
|
|
|
66
71
|
# The field below is required for Flask-security
|
|
67
72
|
# when SECURITY_CONFIRMABLE is True
|
|
68
|
-
confirmed_at = db.DateTimeField()
|
|
73
|
+
confirmed_at = field(db.DateTimeField(), auditable=False)
|
|
69
74
|
|
|
70
|
-
password_rotation_demanded = db.DateTimeField()
|
|
71
|
-
password_rotation_performed = db.DateTimeField()
|
|
75
|
+
password_rotation_demanded = field(db.DateTimeField(), auditable=False)
|
|
76
|
+
password_rotation_performed = field(db.DateTimeField(), auditable=False)
|
|
72
77
|
|
|
73
78
|
# The 5 fields below are required for Flask-security
|
|
74
79
|
# when SECURITY_TRACKABLE is True
|
|
75
|
-
last_login_at = db.DateTimeField()
|
|
76
|
-
current_login_at = db.DateTimeField()
|
|
77
|
-
last_login_ip = db.StringField()
|
|
78
|
-
current_login_ip = db.StringField()
|
|
79
|
-
login_count = db.IntField()
|
|
80
|
+
last_login_at = field(db.DateTimeField(), auditable=False)
|
|
81
|
+
current_login_at = field(db.DateTimeField(), auditable=False)
|
|
82
|
+
last_login_ip = field(db.StringField(), auditable=False)
|
|
83
|
+
current_login_ip = field(db.StringField(), auditable=False)
|
|
84
|
+
login_count = field(db.IntField(), auditable=False)
|
|
80
85
|
|
|
81
|
-
deleted = db.DateTimeField()
|
|
82
|
-
ext = db.MapField(db.GenericEmbeddedDocumentField())
|
|
83
|
-
extras = db.ExtrasField()
|
|
86
|
+
deleted = field(db.DateTimeField())
|
|
87
|
+
ext = field(db.MapField(db.GenericEmbeddedDocumentField()))
|
|
88
|
+
extras = field(db.ExtrasField(), auditable=False)
|
|
84
89
|
|
|
85
90
|
# Used to track notification for automatic inactive users deletion
|
|
86
91
|
# when YEARS_OF_INACTIVITY_BEFORE_DELETION is set
|
|
87
|
-
inactive_deletion_notified_at = db.DateTimeField()
|
|
92
|
+
inactive_deletion_notified_at = field(db.DateTimeField(), auditable=False)
|
|
88
93
|
|
|
89
94
|
before_save = Signal()
|
|
90
95
|
after_save = Signal()
|
|
@@ -209,6 +214,8 @@ class User(WithMetrics, UserMixin, db.Document):
|
|
|
209
214
|
|
|
210
215
|
@classmethod
|
|
211
216
|
def post_save(cls, sender, document, **kwargs):
|
|
217
|
+
if "post_save" in kwargs.get("ignores", []):
|
|
218
|
+
return
|
|
212
219
|
cls.after_save.send(document)
|
|
213
220
|
if kwargs.get("created"):
|
|
214
221
|
cls.on_create.send(document)
|
|
@@ -294,31 +301,31 @@ class User(WithMetrics, UserMixin, db.Document):
|
|
|
294
301
|
from udata.models import Dataset
|
|
295
302
|
|
|
296
303
|
self.metrics["datasets"] = Dataset.objects(owner=self).visible().count()
|
|
297
|
-
self.save()
|
|
304
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
298
305
|
|
|
299
306
|
def count_reuses(self):
|
|
300
307
|
from udata.models import Reuse
|
|
301
308
|
|
|
302
309
|
self.metrics["reuses"] = Reuse.objects(owner=self).visible().count()
|
|
303
|
-
self.save()
|
|
310
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
304
311
|
|
|
305
312
|
def count_dataservices(self):
|
|
306
313
|
from udata.core.dataservices.models import Dataservice
|
|
307
314
|
|
|
308
315
|
self.metrics["dataservices"] = Dataservice.objects(owner=self).visible().count()
|
|
309
|
-
self.save()
|
|
316
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
310
317
|
|
|
311
318
|
def count_followers(self):
|
|
312
319
|
from udata.models import Follow
|
|
313
320
|
|
|
314
321
|
self.metrics["followers"] = Follow.objects(until=None).followers(self).count()
|
|
315
|
-
self.save()
|
|
322
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
316
323
|
|
|
317
324
|
def count_following(self):
|
|
318
325
|
from udata.models import Follow
|
|
319
326
|
|
|
320
327
|
self.metrics["following"] = Follow.objects.following(self).count()
|
|
321
|
-
self.save()
|
|
328
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
322
329
|
|
|
323
330
|
|
|
324
331
|
datastore = MongoEngineUserDatastore(db, User, Role)
|
udata/mongo/datetime_fields.py
CHANGED
|
@@ -362,10 +362,10 @@ class MembershipAPITest:
|
|
|
362
362
|
assert len(members) == 2
|
|
363
363
|
assert members[0]["role"] == "admin"
|
|
364
364
|
assert members[0]["since"] == "2024-04-14T00:00:00+00:00"
|
|
365
|
-
assert members[0]["user"]["email"]
|
|
365
|
+
assert members[0]["user"]["email"] == "***@example.org"
|
|
366
366
|
|
|
367
367
|
assert members[1]["role"] == "editor"
|
|
368
|
-
assert members[1]["user"]["email"]
|
|
368
|
+
assert members[1]["user"]["email"] == "***@example.org"
|
|
369
369
|
|
|
370
370
|
# Super admin of udata can see emails
|
|
371
371
|
api.login(AdminFactory())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: udata
|
|
3
|
-
Version: 10.4.2.
|
|
3
|
+
Version: 10.4.2.dev35319
|
|
4
4
|
Summary: Open data portal
|
|
5
5
|
Home-page: https://github.com/opendatateam/udata
|
|
6
6
|
Author: Opendata Team
|
|
@@ -141,7 +141,9 @@ It is collectively taken care of by members of the
|
|
|
141
141
|
|
|
142
142
|
## Current (in progress)
|
|
143
143
|
|
|
144
|
-
-
|
|
144
|
+
- Add activities to dataservices and resources, add Auditable class to refactor improve code [#3308](https://github.com/opendatateam/udata/pull/3308)
|
|
145
|
+
- Do not crash if file doesn't exists during resource deletion [#3323](https://github.com/opendatateam/udata/pull/3323)
|
|
146
|
+
- Show user domain in suggest [#3324](https://github.com/opendatateam/udata/pull/3324)
|
|
145
147
|
|
|
146
148
|
## 10.4.1 (2025-05-20)
|
|
147
149
|
|
|
@@ -55,12 +55,12 @@ udata/commands/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
55
55
|
udata/commands/tests/test_fixtures.py,sha256=acFmE08S4B0GHJP4nN1dfRNcKjcsE88XusyW_HT12SE,5554
|
|
56
56
|
udata/core/__init__.py,sha256=O7C9WWCXiLWnWPnPbFRszWhOmvRQiI4gD-5qkWvPGRo,385
|
|
57
57
|
udata/core/csv.py,sha256=qeRtSQXPT5n5EoWZ3_XfnOTW7ITnEzzHctODPeX63Uk,8543
|
|
58
|
-
udata/core/owned.py,sha256=
|
|
59
|
-
udata/core/activity/__init__.py,sha256=
|
|
60
|
-
udata/core/activity/api.py,sha256=
|
|
61
|
-
udata/core/activity/models.py,sha256=
|
|
58
|
+
udata/core/owned.py,sha256=OQT7wdk7dAqGvWDiJRVkKJxerDc9_Io910nvLmfBAVI,5561
|
|
59
|
+
udata/core/activity/__init__.py,sha256=U4e1qgBwiz3Lc7lbhIji3p1WVGsUg5GftfDrxFZQu5Q,352
|
|
60
|
+
udata/core/activity/api.py,sha256=ECDnqDlOeWXtjumPZKTyOWHY8mMQr7KFQTF-tbO3jss,3944
|
|
61
|
+
udata/core/activity/models.py,sha256=ipViQMB1UMgxd9nolueanEgOB31s9pihTqkDdfo11u4,3214
|
|
62
62
|
udata/core/activity/signals.py,sha256=Io2A43as3yR-DZ5R3wzM_bTpn528pxWsZDUFZ9xtj2Y,191
|
|
63
|
-
udata/core/activity/tasks.py,sha256=
|
|
63
|
+
udata/core/activity/tasks.py,sha256=F3PY12dnpT5Z8VuYfuOyDP6VPKPJmq1Sm4lSiPfmUCA,1498
|
|
64
64
|
udata/core/badges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
65
65
|
udata/core/badges/api.py,sha256=PkOx-bA9dFXIQZhUYg8r5OLBo1aNJiUJbKVCPOS7wm0,762
|
|
66
66
|
udata/core/badges/commands.py,sha256=J6dKsoZmCbWM3HHOktPt-AHoMj-AhNPL0jxiV4yVlgw,1143
|
|
@@ -81,19 +81,20 @@ udata/core/contact_point/factories.py,sha256=YoW2PKKaemYO4lIy5MwpH36uXM_J3rE-Ihs
|
|
|
81
81
|
udata/core/contact_point/forms.py,sha256=oBe1agSJFyx2QRgYzPRg2A7qVscaBTaKG4V-AyIwnF8,729
|
|
82
82
|
udata/core/contact_point/models.py,sha256=Xqmqg7S13gcaKxiQT52WHeQEHTaUDDGIXInXyqNh4Po,854
|
|
83
83
|
udata/core/dataservices/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
|
+
udata/core/dataservices/activities.py,sha256=0gYJACjhHThQyiXcLD2gX858QjepXu8EUM3nVEp656w,1703
|
|
84
85
|
udata/core/dataservices/api.py,sha256=7jOn3Ug63-5wjXP0d_zL0whdCQeHG_eQ1Y-wNhUuPLM,7388
|
|
85
86
|
udata/core/dataservices/apiv2.py,sha256=XIqJq58uLtxtt52iKYo7Fl0ZXv9Sz72uA7JIGwP8Qos,995
|
|
86
87
|
udata/core/dataservices/constants.py,sha256=LBCTQ44YvjTZfQVbxFiovPxapAknSJNOZKjMP8WeFy4,351
|
|
87
88
|
udata/core/dataservices/csv.py,sha256=pcNIeGaCzBMMna3n3YqHjsoXzfLtg_ITtDmdKb9svDc,1053
|
|
88
89
|
udata/core/dataservices/factories.py,sha256=LDk8vvG0zhW8J-ZX5LoJQDU13pqeIyjQ05muuMro_eA,876
|
|
89
|
-
udata/core/dataservices/models.py,sha256=
|
|
90
|
+
udata/core/dataservices/models.py,sha256=xn4iq9Io9GojUY53U9tBx0ltwJTKICkeiFHltVWejaw,9930
|
|
90
91
|
udata/core/dataservices/permissions.py,sha256=98zM_R4v2ZtRubflB7ajaVQz-DVc-pZBMgtKUYy34oI,169
|
|
91
92
|
udata/core/dataservices/rdf.py,sha256=P-lSAKKy0Kbcfw_tGjl25DuqX77Sg4qZZYJtb9pzcOc,7847
|
|
92
93
|
udata/core/dataservices/search.py,sha256=Wsr51jU14D9NrNP3ELTc8swwtFb-dakgc55cDrtt3No,4639
|
|
93
94
|
udata/core/dataservices/tasks.py,sha256=d2tG1l6u8-eUKUYBOgnCsQLbLmLgJXU-DOzZWhhL6Qg,897
|
|
94
95
|
udata/core/dataset/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
95
96
|
udata/core/dataset/actions.py,sha256=mX6xox0PiMrbcAPZ3VZsI26rfM-ciYfEXxN6sqqImKA,1222
|
|
96
|
-
udata/core/dataset/activities.py,sha256=
|
|
97
|
+
udata/core/dataset/activities.py,sha256=FD5A16cCyPvMY5lEEtQX3ENlaA1pao5Et8FbbHPpLgg,3195
|
|
97
98
|
udata/core/dataset/api.py,sha256=jElQZuguc514Eb0cWdquEfosP1yB79hEQ52SV_SvLx8,33282
|
|
98
99
|
udata/core/dataset/api_fields.py,sha256=SLuzWoPdMLPX28WQ9DRGUPKS27vlltiFeiTo6jXa55Q,17549
|
|
99
100
|
udata/core/dataset/apiv2.py,sha256=YOCNqQh7_OODcrzqJqdAPc0CoB9vG0DVcFiJNl9yBwQ,20749
|
|
@@ -104,7 +105,7 @@ udata/core/dataset/events.py,sha256=bSM0nFEX14r4JHc-bAM-7OOuD3JAxUIpw9GgXbOsUyw,
|
|
|
104
105
|
udata/core/dataset/exceptions.py,sha256=uKiayLSpSzsnLvClObS6hOO0qXEqvURKN7_w8eimQNU,498
|
|
105
106
|
udata/core/dataset/factories.py,sha256=fRDWDlybR_ud4pDs1-ntWuYHKtV9LMHeBOBp2SmTT6M,9006
|
|
106
107
|
udata/core/dataset/forms.py,sha256=7KUxuFcEGT0MUe0cZCiZtsnZhvGgvEd68pe13NgeSMI,6292
|
|
107
|
-
udata/core/dataset/models.py,sha256=
|
|
108
|
+
udata/core/dataset/models.py,sha256=BiqwN1mLffc4_LqtQ6KcNK1BIh04cVKFhyOw3n3BIK8,41161
|
|
108
109
|
udata/core/dataset/permissions.py,sha256=zXQ6kU-Ni3Pl5tDtat-ZPupug9InsNeCN7xRLc2Vcrc,1097
|
|
109
110
|
udata/core/dataset/preview.py,sha256=IwCqiNTjjXbtA_SSKF52pwnzKKEz0GyYM95QNn2Dkog,2561
|
|
110
111
|
udata/core/dataset/rdf.py,sha256=HkjzcWgq9AfPvUGMRI7-ufRrgnlfBmP8crbgRhg6Lz4,31789
|
|
@@ -138,13 +139,13 @@ udata/core/jobs/forms.py,sha256=B-B6jXHZsYV-PWAkD8DLoOlh6trv4l1hGZ4HOPm-PD4,1495
|
|
|
138
139
|
udata/core/jobs/models.py,sha256=xK8T3FCmhtERNbZmh1Tq_ZTO6vojM172tTc0oplNoQ0,1358
|
|
139
140
|
udata/core/metrics/__init__.py,sha256=CB0MhZqNe4gKLBO-6zoGisXK7FtavxWx_Kkkle-INrc,398
|
|
140
141
|
udata/core/metrics/commands.py,sha256=mYozJGER55fHXaQQrlDig4clVFMHWS-8p34XnJGyehs,6003
|
|
141
|
-
udata/core/metrics/models.py,sha256=
|
|
142
|
+
udata/core/metrics/models.py,sha256=Sv2Qhmqdug_atfDc_pyOJCb-8ACpvU-_0FFVPVOFDGk,353
|
|
142
143
|
udata/core/metrics/signals.py,sha256=9mdJW__gR2GJT3huBr6HN2SDhKYJRgNbW9dnh48cAnU,176
|
|
143
144
|
udata/core/metrics/tasks.py,sha256=_3L2g3blndBID66s_CauXEW2bDYathjd4HAAhYNVaOs,827
|
|
144
145
|
udata/core/organization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
145
|
-
udata/core/organization/activities.py,sha256=
|
|
146
|
+
udata/core/organization/activities.py,sha256=Mw4-R8Q6G745IZnCDgrj7h2ax2crGYRhZtcewSK6_Ok,1213
|
|
146
147
|
udata/core/organization/api.py,sha256=7mpPEBglamXWDZrfcp4MUosO3d-1yp4Ywkm06wujstA,20430
|
|
147
|
-
udata/core/organization/api_fields.py,sha256=
|
|
148
|
+
udata/core/organization/api_fields.py,sha256=y-YcBPBPRwDKMdrhIDKWJHe3FAti7qCTvbH5h8zLvOw,8045
|
|
148
149
|
udata/core/organization/apiv2.py,sha256=fLMN5T7s8yF4u7c1WWHXMqQAlyH2OCSyAfmleiFlf7M,3037
|
|
149
150
|
udata/core/organization/commands.py,sha256=DsRAtFDZvTciYNsUWumQWdn0jnNmKW-PwfIHUUZoBb8,1591
|
|
150
151
|
udata/core/organization/constants.py,sha256=fncNtA-vFrRM22K1Wo6iYu9DQZjzknYxH6TIYfxM9kA,563
|
|
@@ -152,7 +153,7 @@ udata/core/organization/csv.py,sha256=YzbdD9Y333QY2j9_sxdmRjAlHg1aGV-oW-dvZdvajV
|
|
|
152
153
|
udata/core/organization/factories.py,sha256=g8ubBcz79xbjvpunZ02IDOakFg1KE6cXjNkE9vFyFAc,624
|
|
153
154
|
udata/core/organization/forms.py,sha256=tscDb1_yOpbTx3ahl8ttA7oDkX9jIyzLc4gOf6WbN3s,3552
|
|
154
155
|
udata/core/organization/metrics.py,sha256=90romzr-FhnPKh-6UHBJ1Af2loDa4-8I1iZEgztA160,1062
|
|
155
|
-
udata/core/organization/models.py,sha256=
|
|
156
|
+
udata/core/organization/models.py,sha256=QFRE8_4caiE34g5t2bNJ_GKk4WCVlobnr5_HZVf2pqw,9616
|
|
156
157
|
udata/core/organization/notifications.py,sha256=i_36-l2y7fOGmnBmr5NDWmGGmrGRaCWbU-6XS4c2wQs,917
|
|
157
158
|
udata/core/organization/permissions.py,sha256=GD-9TMtRppVCPaC1ysXYrONvGJV-ArzAOXm2XMKf9yo,1256
|
|
158
159
|
udata/core/organization/rdf.py,sha256=TF2c85MHAu-TRiHNLxqV_Pw5z6sCgZrNszF9SYspQpk,1936
|
|
@@ -173,7 +174,7 @@ udata/core/reports/api.py,sha256=2xKsYC93V3md2ahkZs--O_0dTWQTicES5_9jbfER3hM,155
|
|
|
173
174
|
udata/core/reports/constants.py,sha256=LRZSX3unyqZeB4yQjK3ws_hGbJcXYk4bu1Rhnhi5DEs,1235
|
|
174
175
|
udata/core/reports/models.py,sha256=AsW5p2ZIdR4c6vNzglEN7MX03It-t9u7ktOsVZqvzSs,2702
|
|
175
176
|
udata/core/reuse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
176
|
-
udata/core/reuse/activities.py,sha256=
|
|
177
|
+
udata/core/reuse/activities.py,sha256=3eh3zS4eH_199rdwIW0vtrYdKAZ8W3qRbrt_CRLNZXY,1486
|
|
177
178
|
udata/core/reuse/api.py,sha256=931cn6CGVcHv0XhSAXyjHkKy-9nOGHaHdE3uMU8v9mc,10928
|
|
178
179
|
udata/core/reuse/api_fields.py,sha256=c61Gl56UjiBpXS0Nbvcoi_QHdUmhnBtqWm6nNHRYKyc,1232
|
|
179
180
|
udata/core/reuse/apiv2.py,sha256=nZe-v8713aXKuv2B578NdxfrIckjbxhS3zUXAKSIKTI,835
|
|
@@ -181,7 +182,7 @@ udata/core/reuse/constants.py,sha256=JgDBrjOKSt9q0auv9rjzbGsch83H-Oi8YXAKeI5hO4o
|
|
|
181
182
|
udata/core/reuse/csv.py,sha256=hCvKYzbL6kiUwzHkd9jrexdeACBj5k7XycKaLZesQeo,892
|
|
182
183
|
udata/core/reuse/factories.py,sha256=GrQqYTIvwQrwkvJrbTr38-2faFW_PC99gn3yOVpgFec,850
|
|
183
184
|
udata/core/reuse/metrics.py,sha256=sVh7BlW3OKRvFDHFyD4pPUV91jOOhj8qeWbBkLPn5Gg,176
|
|
184
|
-
udata/core/reuse/models.py,sha256=
|
|
185
|
+
udata/core/reuse/models.py,sha256=2W0KFQRSUXOfMl-jMWf6TvlgLW4bxoIQ_ceWYq_WdFM,8521
|
|
185
186
|
udata/core/reuse/permissions.py,sha256=j-ancS7gvLl5vJu0TNYqpYD-2So-UzoDE4IHLxRoMGg,621
|
|
186
187
|
udata/core/reuse/search.py,sha256=y1DwXYkBMBwuhn62CULkU1NNo89IYp0Ae7U01jcnjBY,3137
|
|
187
188
|
udata/core/reuse/signals.py,sha256=nDrEUpYKN0AdYiEbrR0z3nzXzjaRcD8SAMutwIDsQPM,155
|
|
@@ -236,16 +237,16 @@ udata/core/topic/models.py,sha256=X-jScC_mMNdZp0hQ_SD-NBHsIPS8aYqpq99x6l4dKz4,20
|
|
|
236
237
|
udata/core/topic/parsers.py,sha256=ugkBd-w8TewInqowNF2w36UPwKYMYluK4U-grkAu-wg,2411
|
|
237
238
|
udata/core/topic/permissions.py,sha256=RtFPPlxuU_Bv7ip6LDO4AoPrKFnIOEs9cCMXaSSmEdk,118
|
|
238
239
|
udata/core/user/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
239
|
-
udata/core/user/activities.py,sha256=
|
|
240
|
-
udata/core/user/api.py,sha256=
|
|
241
|
-
udata/core/user/api_fields.py,sha256=
|
|
240
|
+
udata/core/user/activities.py,sha256=RAotXwFERt6TfLHiNfV13e1AgKGhlGwzlPm-ihj1sHk,3493
|
|
241
|
+
udata/core/user/api.py,sha256=TO5iolYhb7wWSqBwrSHQOSYR-r_8Jrn4LtMF5BiQELk,13473
|
|
242
|
+
udata/core/user/api_fields.py,sha256=S4wONsS5p_Ys31HdBxS_hBreCFHEegjfagg5aprwkNA,6026
|
|
242
243
|
udata/core/user/apiv2.py,sha256=4eNsvJjb4ChJQFrXtVbkOtAvXEcQcQpZf8vkEbriXRA,1125
|
|
243
244
|
udata/core/user/commands.py,sha256=d33hjgUi8WLA8YFvoq__FJJ_E-fk8f4tWo1iuT8jJvM,3156
|
|
244
245
|
udata/core/user/constants.py,sha256=aTluhTR2RGZ_cdG7-mkEoT5Ndbg8BNUwwzCOld0aLMY,77
|
|
245
246
|
udata/core/user/factories.py,sha256=kkwaojciLzfuAOeRnL1E7XCcGPo8waAal_G2eeuVc0k,825
|
|
246
247
|
udata/core/user/forms.py,sha256=yotqZozH9ViKuNI8SwdKocDEi7NXVs_XUMpdr_bIe5s,966
|
|
247
248
|
udata/core/user/metrics.py,sha256=ZuCxYHu-770IsO-KPMhh-ZDkl2hYSVCuHDRvp-H1NfA,1192
|
|
248
|
-
udata/core/user/models.py,sha256=
|
|
249
|
+
udata/core/user/models.py,sha256=vmcpA8oCP9Z3j8d85GdfeRpqGFEzBDTAB4g1wr7baWU,11187
|
|
249
250
|
udata/core/user/permissions.py,sha256=Wbd8bLqSjqp9RHJ6ffLBj74L-LECcAhWazuw4Hq-2Gk,435
|
|
250
251
|
udata/core/user/rdf.py,sha256=F6SDX65ECtVOjDtHXI9WMmq33pN7HWb5ntOqe3LQsbQ,834
|
|
251
252
|
udata/core/user/tasks.py,sha256=VcK0zJq2it4CVmoAMIOgJ0c2IHCV6UBQPAB0qDABD88,3451
|
|
@@ -368,7 +369,7 @@ udata/migrations/__init__.py,sha256=RBCBDaTlLjuMs_Qzwji6Z6T4r7FCGXhESKoxQbT5qAA,
|
|
|
368
369
|
udata/models/__init__.py,sha256=txbZwa-lRG3mq99eQ9E5YcFWiNUdjDVSyJJvlqUMFfs,1413
|
|
369
370
|
udata/mongo/__init__.py,sha256=y4Rv-kq3o_kcEulcNpePLzocXPBNpx3Jd82G-VZPaMc,1421
|
|
370
371
|
udata/mongo/badges_field.py,sha256=UmSaQkiOFtIb116GAT2B0OE6ypOrq8Jx7GdULEr05LU,985
|
|
371
|
-
udata/mongo/datetime_fields.py,sha256=
|
|
372
|
+
udata/mongo/datetime_fields.py,sha256=xACagQZu1OKPvpcznI-bMC1tJfAvo-VBUe7OOadnBdg,2089
|
|
372
373
|
udata/mongo/document.py,sha256=pJJ5B-1EVbQUPMjRqgK51BHPY5eVnyaXgnBnzqwojtA,1777
|
|
373
374
|
udata/mongo/engine.py,sha256=KdvaQnpw6un47CHpu3q9PYWwTtovVmsKZK_k1DmN48M,2552
|
|
374
375
|
udata/mongo/errors.py,sha256=SpTMAc_aNIfGkqyXGCbTlIAmYxU86rGM_NtIYaB642c,472
|
|
@@ -630,7 +631,7 @@ udata/tests/api/test_datasets_api.py,sha256=Igu8NrOfDEtYKjlG7CrL_VpiFN2Tp6W-Kj4r
|
|
|
630
631
|
udata/tests/api/test_fields.py,sha256=OW85Z5MES5HeWOpapeem8OvR1cIcrqW-xMWpdZO4LZ8,1033
|
|
631
632
|
udata/tests/api/test_follow_api.py,sha256=XP6I96JUNT6xjGcQOF7pug_T_i67HzCiOGLaPdpfpEQ,4912
|
|
632
633
|
udata/tests/api/test_me_api.py,sha256=YPd8zmR3zwJKtpSqz8nY1nOOMyXs66INeBwyhg5D0Us,13846
|
|
633
|
-
udata/tests/api/test_organizations_api.py,sha256
|
|
634
|
+
udata/tests/api/test_organizations_api.py,sha256=u27G6Dg3SFmTMCMQXSQWEbsvSonsDelFSojUvt2i05s,41235
|
|
634
635
|
udata/tests/api/test_reports_api.py,sha256=fCSz9NwMXBs6cxdXBVVI6y564AtovmZYw3xkgxQ9KE8,6217
|
|
635
636
|
udata/tests/api/test_reuses_api.py,sha256=x7SG8tNmuPDAaK-t0E_Y02-_5g4o8t7dnVdfgE1Qu08,25218
|
|
636
637
|
udata/tests/api/test_swagger.py,sha256=eE6La9qdTYTIUFevRVPJgtj17Jq_8uOlsDwzCNR0LL8,760
|
|
@@ -726,9 +727,9 @@ udata/translations/pt/LC_MESSAGES/udata.mo,sha256=ViV14tUmjSydHS0TWG_mFikKQfyUaT
|
|
|
726
727
|
udata/translations/pt/LC_MESSAGES/udata.po,sha256=rzAD_MVoV54TmN3w1ECz3H2Ru5pM7hWMVH03SkY28Q8,47250
|
|
727
728
|
udata/translations/sr/LC_MESSAGES/udata.mo,sha256=EHX1_D-Uglj38832G7BrA0QC5IuY3p8dKqi9T0DgPmE,29169
|
|
728
729
|
udata/translations/sr/LC_MESSAGES/udata.po,sha256=3PMnbVhKVJh6Q8ABi1ZTZ8Dcf-sMjngLJZqLbonJoec,54225
|
|
729
|
-
udata-10.4.2.
|
|
730
|
-
udata-10.4.2.
|
|
731
|
-
udata-10.4.2.
|
|
732
|
-
udata-10.4.2.
|
|
733
|
-
udata-10.4.2.
|
|
734
|
-
udata-10.4.2.
|
|
730
|
+
udata-10.4.2.dev35319.dist-info/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
|
|
731
|
+
udata-10.4.2.dev35319.dist-info/METADATA,sha256=Au8KhpPvrAdVMP-YYk8a1wOly-4_2_zAhZ1SoALR0V8,146838
|
|
732
|
+
udata-10.4.2.dev35319.dist-info/WHEEL,sha256=Kh9pAotZVRFj97E15yTA4iADqXdQfIVTHcNaZTjxeGM,110
|
|
733
|
+
udata-10.4.2.dev35319.dist-info/entry_points.txt,sha256=ETvkR4r6G1duBsh_V_fGWENQy17GTFuobi95MYBAl1A,498
|
|
734
|
+
udata-10.4.2.dev35319.dist-info/top_level.txt,sha256=39OCg-VWFWOq4gCKnjKNu-s3OwFlZIu_dVH8Gl6ndHw,12
|
|
735
|
+
udata-10.4.2.dev35319.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|