udata 10.8.2.dev37076__py2.py3-none-any.whl → 10.8.3__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/__init__.py +1 -1
- udata/core/activity/models.py +23 -1
- udata/core/dataset/api_fields.py +2 -0
- udata/core/dataset/apiv2.py +4 -0
- udata/core/dataset/constants.py +1 -0
- udata/core/dataset/csv.py +1 -0
- udata/core/dataset/forms.py +6 -0
- udata/core/dataset/metrics.py +34 -0
- udata/core/dataset/models.py +10 -0
- udata/core/dataset/tasks.py +0 -11
- udata/core/metrics/__init__.py +1 -0
- udata/core/metrics/commands.py +3 -0
- udata/core/organization/csv.py +9 -26
- udata/core/organization/metrics.py +2 -0
- udata/core/organization/models.py +14 -9
- udata/core/user/metrics.py +2 -0
- udata/harvest/backends/dcat.py +161 -165
- udata/harvest/tests/dcat/catalog.xml +1 -0
- udata/harvest/tests/test_dcat_backend.py +19 -6
- udata/settings.py +1 -1
- udata/static/chunks/{13.2d06442dd9a05d9777b5.js → 13.d9c1735d14038b94c17e.js} +2 -2
- udata/static/chunks/{13.2d06442dd9a05d9777b5.js.map → 13.d9c1735d14038b94c17e.js.map} +1 -1
- udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js → 17.81c57c0dedf812e43013.js} +2 -2
- udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js.map → 17.81c57c0dedf812e43013.js.map} +1 -1
- udata/static/chunks/{19.f03a102365af4315f9db.js → 19.8d03c06efcac6884bebe.js} +3 -3
- udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.8d03c06efcac6884bebe.js.map} +1 -1
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.343ca020a2d38cec1a14.js} +3 -3
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.343ca020a2d38cec1a14.js.map} +1 -1
- udata/static/chunks/{6.d663709d877baa44a71e.js → 6.a3b07de9dd2ca2d24e85.js} +3 -3
- udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.a3b07de9dd2ca2d24e85.js.map} +1 -1
- udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.b966402f5d680d4bdf4a.js} +2 -2
- udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.b966402f5d680d4bdf4a.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/dataset/test_dataset_model.py +63 -1
- udata/tests/organization/test_csv_adapter.py +3 -15
- udata/tests/reuse/test_reuse_model.py +6 -4
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +62 -54
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +62 -54
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +62 -54
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +62 -54
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +62 -54
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +62 -54
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +62 -54
- udata/translations/udata.pot +63 -56
- udata/utils.py +16 -0
- {udata-10.8.2.dev37076.dist-info → udata-10.8.3.dist-info}/METADATA +15 -3
- {udata-10.8.2.dev37076.dist-info → udata-10.8.3.dist-info}/RECORD +59 -58
- {udata-10.8.2.dev37076.dist-info → udata-10.8.3.dist-info}/LICENSE +0 -0
- {udata-10.8.2.dev37076.dist-info → udata-10.8.3.dist-info}/WHEEL +0 -0
- {udata-10.8.2.dev37076.dist-info → udata-10.8.3.dist-info}/entry_points.txt +0 -0
- {udata-10.8.2.dev37076.dist-info → udata-10.8.3.dist-info}/top_level.txt +0 -0
udata/__init__.py
CHANGED
udata/core/activity/models.py
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
2
|
|
|
3
3
|
from blinker import Signal
|
|
4
|
+
from mongoengine.errors import DoesNotExist
|
|
4
5
|
from mongoengine.signals import post_save
|
|
5
6
|
|
|
6
7
|
from udata.api_fields import get_fields
|
|
7
8
|
from udata.auth import current_user
|
|
8
9
|
from udata.mongo import db
|
|
10
|
+
from udata.utils import get_field_value_from_path
|
|
9
11
|
|
|
10
12
|
from .signals import new_activity
|
|
11
13
|
|
|
@@ -79,6 +81,25 @@ class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
|
|
|
79
81
|
|
|
80
82
|
|
|
81
83
|
class Auditable(object):
|
|
84
|
+
def clean(self, **kwargs):
|
|
85
|
+
super().clean()
|
|
86
|
+
"""
|
|
87
|
+
Fetch original document changed fields values before the new one erase it.
|
|
88
|
+
"""
|
|
89
|
+
changed_fields = self._get_changed_fields()
|
|
90
|
+
if changed_fields:
|
|
91
|
+
try:
|
|
92
|
+
# `only` does not support having nested list as expressed in changed fields, ex resources.0.title
|
|
93
|
+
# thus we only strip to direct attributes for simplicity
|
|
94
|
+
direct_attributes = set(field.split(".")[0] for field in changed_fields)
|
|
95
|
+
old_document = self.__class__.objects.only(*direct_attributes).get(pk=self.pk)
|
|
96
|
+
self._previous_changed_fields = {}
|
|
97
|
+
for field_path in changed_fields:
|
|
98
|
+
field_value = get_field_value_from_path(old_document, field_path)
|
|
99
|
+
self._previous_changed_fields[field_path] = field_value
|
|
100
|
+
except DoesNotExist:
|
|
101
|
+
pass
|
|
102
|
+
|
|
82
103
|
@classmethod
|
|
83
104
|
def post_save(cls, sender, document, **kwargs):
|
|
84
105
|
try:
|
|
@@ -97,6 +118,7 @@ class Auditable(object):
|
|
|
97
118
|
if kwargs.get("created"):
|
|
98
119
|
cls.on_create.send(document)
|
|
99
120
|
elif len(changed_fields):
|
|
100
|
-
|
|
121
|
+
previous = getattr(document, "_previous_changed_fields", None)
|
|
122
|
+
cls.on_update.send(document, changed_fields=changed_fields, previous=previous)
|
|
101
123
|
if getattr(document, "deleted_at", None) or getattr(document, "deleted", None):
|
|
102
124
|
cls.on_delete.send(document)
|
udata/core/dataset/api_fields.py
CHANGED
|
@@ -266,6 +266,7 @@ DEFAULT_MASK = ",".join(
|
|
|
266
266
|
"acronym",
|
|
267
267
|
"slug",
|
|
268
268
|
"description",
|
|
269
|
+
"description_short",
|
|
269
270
|
"created_at",
|
|
270
271
|
"last_modified",
|
|
271
272
|
"deleted",
|
|
@@ -327,6 +328,7 @@ dataset_fields = api.model(
|
|
|
327
328
|
"description": fields.Markdown(
|
|
328
329
|
description="The dataset description in markdown", required=True
|
|
329
330
|
),
|
|
331
|
+
"description_short": fields.String(description="The dataset short description"),
|
|
330
332
|
"created_at": fields.ISODateTime(
|
|
331
333
|
description="This date is computed between harvested creation date if any and site's internal creation date",
|
|
332
334
|
required=True,
|
udata/core/dataset/apiv2.py
CHANGED
|
@@ -44,6 +44,7 @@ DEFAULT_MASK_APIV2 = ",".join(
|
|
|
44
44
|
"acronym",
|
|
45
45
|
"slug",
|
|
46
46
|
"description",
|
|
47
|
+
"description_short",
|
|
47
48
|
"created_at",
|
|
48
49
|
"last_modified",
|
|
49
50
|
"deleted",
|
|
@@ -105,6 +106,9 @@ dataset_fields = apiv2.model(
|
|
|
105
106
|
"description": fields.Markdown(
|
|
106
107
|
description="The dataset description in markdown", required=True
|
|
107
108
|
),
|
|
109
|
+
"description_short": fields.String(
|
|
110
|
+
description="The dataset short description", required=False
|
|
111
|
+
),
|
|
108
112
|
"created_at": fields.ISODateTime(
|
|
109
113
|
description="The dataset creation date", required=True, readonly=True
|
|
110
114
|
),
|
udata/core/dataset/constants.py
CHANGED
udata/core/dataset/csv.py
CHANGED
udata/core/dataset/forms.py
CHANGED
|
@@ -7,6 +7,7 @@ from udata.mongo.errors import FieldValidationError
|
|
|
7
7
|
from .constants import (
|
|
8
8
|
CHECKSUM_TYPES,
|
|
9
9
|
DEFAULT_FREQUENCY,
|
|
10
|
+
DESCRIPTION_SHORT_SIZE_LIMIT,
|
|
10
11
|
DESCRIPTION_SIZE_LIMIT,
|
|
11
12
|
LEGACY_FREQUENCIES,
|
|
12
13
|
RESOURCE_FILETYPES,
|
|
@@ -151,6 +152,11 @@ class DatasetForm(ModelForm):
|
|
|
151
152
|
[validators.DataRequired(), validators.Length(max=DESCRIPTION_SIZE_LIMIT)],
|
|
152
153
|
description=_("The details about the dataset (collection process, specifics...)."),
|
|
153
154
|
)
|
|
155
|
+
description_short = fields.StringField(
|
|
156
|
+
_("Short description"),
|
|
157
|
+
[validators.Length(max=DESCRIPTION_SHORT_SIZE_LIMIT)],
|
|
158
|
+
description=_("A short description of the dataset."),
|
|
159
|
+
)
|
|
154
160
|
license = fields.ModelSelectField(_("License"), model=License, allow_blank=True)
|
|
155
161
|
frequency = fields.SelectField(
|
|
156
162
|
_("Update frequency"),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from udata.core.dataservices.models import Dataservice
|
|
2
|
+
from udata.core.reuse.models import Reuse
|
|
3
|
+
|
|
4
|
+
from .models import Dataset
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@Reuse.on_create.connect
|
|
8
|
+
@Reuse.on_update.connect
|
|
9
|
+
@Reuse.on_delete.connect
|
|
10
|
+
def update_dataset_reuses_metric(reuse, **kwargs):
|
|
11
|
+
previous = kwargs.get("previous")
|
|
12
|
+
if previous and previous.get("datasets"):
|
|
13
|
+
datasets_delta = set(dat.id for dat in reuse.datasets).symmetric_difference(
|
|
14
|
+
set(dat.id for dat in previous["datasets"])
|
|
15
|
+
)
|
|
16
|
+
else:
|
|
17
|
+
datasets_delta = set(dat.id for dat in reuse.datasets)
|
|
18
|
+
for dataset in datasets_delta:
|
|
19
|
+
Dataset.get(dataset).count_reuses()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@Dataservice.on_create.connect
|
|
23
|
+
@Dataservice.on_update.connect
|
|
24
|
+
@Dataservice.on_delete.connect
|
|
25
|
+
def update_dataset_dataservices_metric(dataservice, **kwargs):
|
|
26
|
+
previous = kwargs.get("previous")
|
|
27
|
+
if previous and previous.get("datasets"):
|
|
28
|
+
datasets_delta = set(dat.id for dat in dataservice.datasets).symmetric_difference(
|
|
29
|
+
set(dat.id for dat in previous["datasets"])
|
|
30
|
+
)
|
|
31
|
+
else:
|
|
32
|
+
datasets_delta = set(dat.id for dat in dataservice.datasets)
|
|
33
|
+
for dataset in datasets_delta:
|
|
34
|
+
Dataset.get(dataset).count_dataservices()
|
udata/core/dataset/models.py
CHANGED
|
@@ -35,6 +35,7 @@ from .constants import (
|
|
|
35
35
|
CHECKSUM_TYPES,
|
|
36
36
|
CLOSED_FORMATS,
|
|
37
37
|
DEFAULT_LICENSE,
|
|
38
|
+
DESCRIPTION_SHORT_SIZE_LIMIT,
|
|
38
39
|
LEGACY_FREQUENCIES,
|
|
39
40
|
MAX_DISTANCE,
|
|
40
41
|
PIVOTAL_DATA,
|
|
@@ -560,6 +561,7 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
|
|
|
560
561
|
auditable=False,
|
|
561
562
|
)
|
|
562
563
|
description = field(db.StringField(required=True, default=""))
|
|
564
|
+
description_short = field(db.StringField(max_length=DESCRIPTION_SHORT_SIZE_LIMIT))
|
|
563
565
|
license = field(db.ReferenceField("License"))
|
|
564
566
|
|
|
565
567
|
tags = field(db.TagListField())
|
|
@@ -616,6 +618,7 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
|
|
|
616
618
|
"discussions_open",
|
|
617
619
|
"reuses",
|
|
618
620
|
"reuses_by_months",
|
|
621
|
+
"dataservices",
|
|
619
622
|
"followers",
|
|
620
623
|
"followers_by_months",
|
|
621
624
|
"views",
|
|
@@ -628,6 +631,7 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
|
|
|
628
631
|
"created_at_internal",
|
|
629
632
|
"last_modified_internal",
|
|
630
633
|
"metrics.reuses",
|
|
634
|
+
"metrics.dataservices",
|
|
631
635
|
"metrics.followers",
|
|
632
636
|
"metrics.views",
|
|
633
637
|
"slug",
|
|
@@ -1100,6 +1104,12 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
|
|
|
1100
1104
|
self.metrics["reuses_by_months"] = get_stock_metrics(Reuse.objects(datasets=self).visible())
|
|
1101
1105
|
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
1102
1106
|
|
|
1107
|
+
def count_dataservices(self):
|
|
1108
|
+
from udata.core.dataservices.models import Dataservice
|
|
1109
|
+
|
|
1110
|
+
self.metrics["dataservices"] = Dataservice.objects(datasets=self).visible().count()
|
|
1111
|
+
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
1112
|
+
|
|
1103
1113
|
def count_followers(self):
|
|
1104
1114
|
from udata.models import Follow
|
|
1105
1115
|
|
udata/core/dataset/tasks.py
CHANGED
|
@@ -123,17 +123,6 @@ def send_frequency_reminder(self):
|
|
|
123
123
|
print("Done")
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
@job("update-datasets-reuses-metrics")
|
|
127
|
-
def update_datasets_reuses_metrics(self):
|
|
128
|
-
all_datasets = Dataset.objects.visible().timeout(False)
|
|
129
|
-
for dataset in all_datasets:
|
|
130
|
-
try:
|
|
131
|
-
dataset.count_reuses()
|
|
132
|
-
except Exception as e:
|
|
133
|
-
log.error(f"Error for dataset {dataset} during reuses metrics update: {e}")
|
|
134
|
-
continue
|
|
135
|
-
|
|
136
|
-
|
|
137
126
|
def get_queryset(model_cls):
|
|
138
127
|
# special case for resources
|
|
139
128
|
if model_cls.__name__ == "Resource":
|
udata/core/metrics/__init__.py
CHANGED
|
@@ -6,6 +6,7 @@ def init_app(app):
|
|
|
6
6
|
import udata.core.user.metrics # noqa
|
|
7
7
|
import udata.core.organization.metrics # noqa
|
|
8
8
|
import udata.core.discussions.metrics # noqa
|
|
9
|
+
import udata.core.dataset.metrics # noqa
|
|
9
10
|
import udata.core.reuse.metrics # noqa
|
|
10
11
|
import udata.core.followers.metrics # noqa
|
|
11
12
|
|
udata/core/metrics/commands.py
CHANGED
|
@@ -74,6 +74,7 @@ def update(
|
|
|
74
74
|
dataset.metrics.clear()
|
|
75
75
|
dataset.count_discussions()
|
|
76
76
|
dataset.count_reuses()
|
|
77
|
+
dataset.count_dataservices()
|
|
77
78
|
dataset.count_followers()
|
|
78
79
|
except Exception as e:
|
|
79
80
|
log.info(f"Error during update: {e}")
|
|
@@ -119,6 +120,7 @@ def update(
|
|
|
119
120
|
organization.metrics.clear()
|
|
120
121
|
organization.count_datasets()
|
|
121
122
|
organization.count_reuses()
|
|
123
|
+
organization.count_dataservices()
|
|
122
124
|
organization.count_followers()
|
|
123
125
|
organization.count_members()
|
|
124
126
|
except Exception as e:
|
|
@@ -135,6 +137,7 @@ def update(
|
|
|
135
137
|
user.metrics.clear()
|
|
136
138
|
user.count_datasets()
|
|
137
139
|
user.count_reuses()
|
|
140
|
+
user.count_dataservices()
|
|
138
141
|
user.count_followers()
|
|
139
142
|
user.count_following()
|
|
140
143
|
except Exception as e:
|
udata/core/organization/csv.py
CHANGED
|
@@ -4,6 +4,13 @@ from udata.core.dataset.models import Dataset
|
|
|
4
4
|
from .models import Organization
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
def get_resource_download_count(organization: Organization) -> int:
|
|
8
|
+
return sum(
|
|
9
|
+
dat.metrics.get("resources_downloads", 0) or 0
|
|
10
|
+
for dat in Dataset.objects(organization=organization).only("metrics").visible()
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
7
14
|
@csv.adapter(Organization)
|
|
8
15
|
class OrganizationCsvAdapter(csv.Adapter):
|
|
9
16
|
downloads_counts = None
|
|
@@ -21,32 +28,8 @@ class OrganizationCsvAdapter(csv.Adapter):
|
|
|
21
28
|
"last_modified",
|
|
22
29
|
"business_number_id",
|
|
23
30
|
("members_count", lambda o: len(o.members)),
|
|
31
|
+
("downloads", get_resource_download_count),
|
|
24
32
|
)
|
|
25
33
|
|
|
26
34
|
def dynamic_fields(self):
|
|
27
|
-
return csv.metric_fields(Organization)
|
|
28
|
-
|
|
29
|
-
def get_dynamic_field_downloads(self):
|
|
30
|
-
downloads_counts = self.get_downloads_counts()
|
|
31
|
-
return [("downloads", lambda o: downloads_counts.get(str(o.id), 0))]
|
|
32
|
-
|
|
33
|
-
def get_downloads_counts(self):
|
|
34
|
-
"""
|
|
35
|
-
Prefetch all the resources' downloads for all selected organization into memory
|
|
36
|
-
"""
|
|
37
|
-
if self.downloads_counts is not None:
|
|
38
|
-
return self.downloads_counts
|
|
39
|
-
|
|
40
|
-
self.downloads_counts = {}
|
|
41
|
-
|
|
42
|
-
ids = [o.id for o in self.queryset]
|
|
43
|
-
for dataset in Dataset.objects(organization__in=ids):
|
|
44
|
-
org_id = str(dataset.organization.id)
|
|
45
|
-
if self.downloads_counts.get(org_id) is None:
|
|
46
|
-
self.downloads_counts[org_id] = 0
|
|
47
|
-
|
|
48
|
-
self.downloads_counts[org_id] += sum(
|
|
49
|
-
resource.metrics.get("views", 0) for resource in dataset.resources
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
return self.downloads_counts
|
|
35
|
+
return csv.metric_fields(Organization)
|
|
@@ -188,6 +188,10 @@ class Organization(
|
|
|
188
188
|
after_delete = Signal()
|
|
189
189
|
on_delete = Signal()
|
|
190
190
|
|
|
191
|
+
def __init__(self, *args, **kwargs):
|
|
192
|
+
super().__init__(*args, **kwargs)
|
|
193
|
+
self.compute_aggregate_metrics = True
|
|
194
|
+
|
|
191
195
|
@classmethod
|
|
192
196
|
def pre_save(cls, sender, document, **kwargs):
|
|
193
197
|
cls.before_save.send(document)
|
|
@@ -307,15 +311,16 @@ class Organization(
|
|
|
307
311
|
from udata.models import Dataset, Follow, Reuse
|
|
308
312
|
|
|
309
313
|
self.metrics["datasets"] = Dataset.objects(organization=self).visible().count()
|
|
310
|
-
self.
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
if self.compute_aggregate_metrics:
|
|
315
|
+
self.metrics["datasets_by_months"] = get_stock_metrics(
|
|
316
|
+
Dataset.objects(organization=self).visible(), date_label="created_at_internal"
|
|
317
|
+
)
|
|
318
|
+
self.metrics["datasets_followers_by_months"] = get_stock_metrics(
|
|
319
|
+
Follow.objects(following__in=Dataset.objects(organization=self)), date_label="since"
|
|
320
|
+
)
|
|
321
|
+
self.metrics["datasets_reuses_by_months"] = get_stock_metrics(
|
|
322
|
+
Reuse.objects(datasets__in=Dataset.objects(organization=self)).visible()
|
|
323
|
+
)
|
|
319
324
|
|
|
320
325
|
self.save(signal_kwargs={"ignores": ["post_save"]})
|
|
321
326
|
|