udata 10.4.2.dev35274__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.

Files changed (39) hide show
  1. udata/core/activity/__init__.py +1 -0
  2. udata/core/activity/api.py +1 -0
  3. udata/core/activity/models.py +28 -1
  4. udata/core/activity/tasks.py +19 -4
  5. udata/core/dataservices/activities.py +53 -0
  6. udata/core/dataservices/models.py +16 -20
  7. udata/core/dataset/activities.py +49 -2
  8. udata/core/dataset/models.py +43 -42
  9. udata/core/metrics/models.py +1 -0
  10. udata/core/organization/activities.py +3 -2
  11. udata/core/organization/models.py +31 -31
  12. udata/core/owned.py +1 -1
  13. udata/core/reuse/activities.py +3 -2
  14. udata/core/reuse/models.py +8 -16
  15. udata/core/user/activities.py +17 -1
  16. udata/core/user/models.py +39 -32
  17. udata/mongo/datetime_fields.py +1 -0
  18. udata/static/chunks/{11.8a2f7828175824bcd74b.js → 11.b6f741fcc366abfad9c4.js} +3 -3
  19. udata/static/chunks/{11.8a2f7828175824bcd74b.js.map → 11.b6f741fcc366abfad9c4.js.map} +1 -1
  20. udata/static/chunks/{13.39e106d56f794ebd06a0.js → 13.2d06442dd9a05d9777b5.js} +2 -2
  21. udata/static/chunks/{13.39e106d56f794ebd06a0.js.map → 13.2d06442dd9a05d9777b5.js.map} +1 -1
  22. udata/static/chunks/{17.70cbb4a91b002338007e.js → 17.e8e4caaad5cb0cc0bacc.js} +2 -2
  23. udata/static/chunks/{17.70cbb4a91b002338007e.js.map → 17.e8e4caaad5cb0cc0bacc.js.map} +1 -1
  24. udata/static/chunks/{19.df16abde17a42033a7f8.js → 19.f03a102365af4315f9db.js} +3 -3
  25. udata/static/chunks/{19.df16abde17a42033a7f8.js.map → 19.f03a102365af4315f9db.js.map} +1 -1
  26. udata/static/chunks/{5.5660483641193b7f8295.js → 5.0fa1408dae4e76b87b2e.js} +3 -3
  27. udata/static/chunks/{5.5660483641193b7f8295.js.map → 5.0fa1408dae4e76b87b2e.js.map} +1 -1
  28. udata/static/chunks/{6.30dce49d17db07600b06.js → 6.d663709d877baa44a71e.js} +3 -3
  29. udata/static/chunks/{6.30dce49d17db07600b06.js.map → 6.d663709d877baa44a71e.js.map} +1 -1
  30. udata/static/chunks/{8.54e44b102164ae5e7a67.js → 8.778091d55cd8ea39af6b.js} +2 -2
  31. udata/static/chunks/{8.54e44b102164ae5e7a67.js.map → 8.778091d55cd8ea39af6b.js.map} +1 -1
  32. udata/static/common.js +1 -1
  33. udata/static/common.js.map +1 -1
  34. {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35319.dist-info}/METADATA +3 -2
  35. {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35319.dist-info}/RECORD +39 -38
  36. {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35319.dist-info}/LICENSE +0 -0
  37. {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35319.dist-info}/WHEEL +0 -0
  38. {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35319.dist-info}/entry_points.txt +0 -0
  39. {udata-10.4.2.dev35274.dist-info → udata-10.4.2.dev35319.dist-info}/top_level.txt +0 -0
@@ -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
@@ -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
  )
@@ -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)
@@ -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, actor_id, related_to_cls, related_to_id, organization_id=None, extras=None
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(actor=actor, related_to=related_to, organization=organization, extras=extras)
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(db.ExtrasField())
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)
@@ -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
@@ -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 = db.SlugField(
549
- max_length=255, required=True, populate_from="title", update=True, follow=True
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 = db.BooleanField(required=True, default=False)
574
+ featured = field(
575
+ db.BooleanField(required=True, default=False),
576
+ auditable=False,
577
+ )
571
578
 
572
- contact_points = db.ListField(db.ReferenceField("ContactPoint", reverse_delete_rule=db.PULL))
579
+ contact_points = field(
580
+ db.ListField(db.ReferenceField("ContactPoint", reverse_delete_rule=db.PULL))
581
+ )
573
582
 
574
- created_at_internal = DateTimeField(
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 = DateTimeField(
578
- verbose_name=_("Last modification date"), default=datetime.utcnow, required=True
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)
@@ -8,6 +8,7 @@ class WithMetrics(object):
8
8
  metrics = field(
9
9
  db.DictField(),
10
10
  readonly=True,
11
+ auditable=False,
11
12
  )
12
13
 
13
14
  __metrics_keys__ = []
@@ -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 = db.SlugField(
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 = db.ImageField(
123
- fs=avatars, basename=default_image_basename, max_size=LOGO_MAX_SIZE, thumbnails=LOGO_SIZES
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 behvaior between users and organizations.
83
+ A mixin to factorize owning behavior between users and organizations.
84
84
  """
85
85
 
86
86
  owner = field(
@@ -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