udata 12.0.2.dev15__py3-none-any.whl → 13.0.1.dev21__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/api/__init__.py +1 -0
- udata/api_fields.py +10 -4
- udata/app.py +11 -10
- udata/auth/__init__.py +9 -10
- udata/auth/mails.py +137 -45
- udata/auth/views.py +5 -12
- udata/commands/__init__.py +2 -3
- udata/commands/info.py +1 -3
- udata/commands/tests/test_fixtures.py +6 -3
- udata/core/access_type/api.py +18 -0
- udata/core/access_type/constants.py +98 -0
- udata/core/access_type/models.py +44 -0
- udata/core/activity/models.py +1 -1
- udata/core/badges/models.py +1 -1
- udata/core/badges/tasks.py +35 -1
- udata/core/badges/tests/test_commands.py +2 -4
- udata/core/badges/tests/test_model.py +2 -2
- udata/core/badges/tests/test_tasks.py +55 -0
- udata/core/constants.py +1 -0
- udata/core/contact_point/models.py +8 -0
- udata/core/dataservices/api.py +3 -3
- udata/core/dataservices/apiv2.py +3 -1
- udata/core/dataservices/constants.py +0 -29
- udata/core/dataservices/models.py +44 -44
- udata/core/dataservices/rdf.py +2 -1
- udata/core/dataservices/search.py +5 -9
- udata/core/dataservices/tasks.py +33 -0
- udata/core/dataset/api_fields.py +11 -0
- udata/core/dataset/apiv2.py +11 -0
- udata/core/dataset/constants.py +0 -1
- udata/core/dataset/forms.py +29 -0
- udata/core/dataset/models.py +16 -4
- udata/core/dataset/rdf.py +2 -1
- udata/core/dataset/search.py +2 -2
- udata/core/dataset/tasks.py +86 -8
- udata/core/discussions/mails.py +63 -0
- udata/core/discussions/tasks.py +4 -18
- udata/core/metrics/__init__.py +0 -6
- udata/core/organization/api.py +3 -1
- udata/core/organization/mails.py +144 -0
- udata/core/organization/models.py +2 -1
- udata/core/organization/search.py +1 -1
- udata/core/organization/tasks.py +21 -49
- udata/core/pages/tests/test_api.py +0 -2
- udata/core/reuse/api.py +27 -1
- udata/core/reuse/mails.py +21 -0
- udata/core/reuse/models.py +10 -1
- udata/core/reuse/search.py +1 -1
- udata/core/reuse/tasks.py +2 -3
- udata/core/site/models.py +2 -6
- udata/core/spatial/tests/test_api.py +17 -20
- udata/core/spatial/tests/test_models.py +3 -3
- udata/core/user/mails.py +54 -0
- udata/core/user/models.py +2 -3
- udata/core/user/tasks.py +8 -23
- udata/core/user/tests/test_user_model.py +2 -6
- udata/entrypoints.py +0 -5
- udata/features/identicon/tests/test_backends.py +3 -13
- udata/forms/fields.py +3 -3
- udata/forms/widgets.py +2 -2
- udata/frontend/__init__.py +3 -32
- udata/harvest/actions.py +4 -9
- udata/harvest/api.py +5 -14
- udata/harvest/backends/__init__.py +20 -11
- udata/harvest/backends/base.py +2 -2
- udata/harvest/backends/ckan/harvesters.py +2 -1
- udata/harvest/backends/dcat.py +3 -0
- udata/harvest/backends/maaf.py +1 -0
- udata/harvest/commands.py +6 -4
- udata/harvest/forms.py +9 -6
- udata/harvest/tasks.py +3 -5
- udata/harvest/tests/ckan/test_ckan_backend.py +300 -337
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +94 -99
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +128 -122
- udata/harvest/tests/ckan/test_dkan_backend.py +39 -51
- udata/harvest/tests/dcat/datara--5a26b0f6-0ccf-46ad-ac58-734054b91977.rdf.xml +255 -0
- udata/harvest/tests/dcat/datara--f40c3860-7236-4b30-a141-23b8ae33f7b2.rdf.xml +289 -0
- udata/harvest/tests/factories.py +1 -1
- udata/harvest/tests/test_actions.py +11 -9
- udata/harvest/tests/test_api.py +4 -5
- udata/harvest/tests/test_base_backend.py +5 -4
- udata/harvest/tests/test_dcat_backend.py +50 -19
- udata/harvest/tests/test_models.py +2 -4
- udata/harvest/tests/test_notifications.py +2 -4
- udata/harvest/tests/test_tasks.py +2 -3
- udata/mail.py +90 -53
- udata/migrations/2025-01-05-dataservices-fields-changes.py +8 -14
- udata/migrations/2025-10-21-remove-ckan-harvest-modified-at.py +28 -0
- udata/migrations/2025-10-29-harvesters-sources-integrity.py +27 -0
- udata/mongo/taglist_field.py +3 -3
- udata/rdf.py +32 -15
- udata/sentry.py +3 -4
- udata/settings.py +7 -2
- udata/tags.py +5 -5
- udata/tasks.py +3 -3
- udata/templates/mail/message.html +65 -0
- udata/templates/mail/message.txt +16 -0
- udata/tests/__init__.py +40 -58
- udata/tests/api/__init__.py +87 -2
- udata/tests/api/test_activities_api.py +17 -23
- udata/tests/api/test_auth_api.py +2 -4
- udata/tests/api/test_contact_points.py +48 -54
- udata/tests/api/test_dataservices_api.py +57 -37
- udata/tests/api/test_datasets_api.py +146 -49
- udata/tests/api/test_me_api.py +4 -6
- udata/tests/api/test_organizations_api.py +19 -38
- udata/tests/api/test_reports_api.py +0 -4
- udata/tests/api/test_reuses_api.py +92 -19
- udata/tests/api/test_security_api.py +124 -0
- udata/tests/api/test_swagger.py +2 -3
- udata/tests/api/test_tags_api.py +6 -7
- udata/tests/api/test_transfer_api.py +0 -2
- udata/tests/api/test_user_api.py +8 -10
- udata/tests/apiv2/test_datasets.py +0 -4
- udata/tests/apiv2/test_me_api.py +0 -2
- udata/tests/apiv2/test_organizations.py +0 -2
- udata/tests/apiv2/test_swagger.py +2 -3
- udata/tests/apiv2/test_topics.py +0 -2
- udata/tests/cli/test_cli_base.py +14 -12
- udata/tests/cli/test_db_cli.py +51 -54
- udata/tests/contact_point/test_contact_point_models.py +2 -2
- udata/tests/dataservice/test_csv_adapter.py +2 -5
- udata/tests/dataservice/test_dataservice_rdf.py +8 -6
- udata/tests/dataservice/test_dataservice_tasks.py +36 -38
- udata/tests/dataset/test_csv_adapter.py +2 -5
- udata/tests/dataset/test_dataset_actions.py +2 -4
- udata/tests/dataset/test_dataset_commands.py +2 -4
- udata/tests/dataset/test_dataset_events.py +3 -3
- udata/tests/dataset/test_dataset_model.py +6 -7
- udata/tests/dataset/test_dataset_rdf.py +201 -12
- udata/tests/dataset/test_dataset_recommendations.py +2 -2
- udata/tests/dataset/test_dataset_tasks.py +66 -68
- udata/tests/dataset/test_resource_preview.py +39 -48
- udata/tests/dataset/test_transport_tasks.py +2 -2
- udata/tests/features/territories/__init__.py +0 -6
- udata/tests/features/territories/test_territories_api.py +25 -24
- udata/tests/forms/test_current_user_field.py +2 -2
- udata/tests/forms/test_dict_field.py +2 -4
- udata/tests/forms/test_extras_fields.py +2 -3
- udata/tests/forms/test_image_field.py +2 -2
- udata/tests/forms/test_model_field.py +2 -4
- udata/tests/forms/test_publish_as_field.py +2 -4
- udata/tests/forms/test_user_forms.py +26 -29
- udata/tests/frontend/test_auth.py +2 -3
- udata/tests/frontend/test_csv.py +5 -6
- udata/tests/frontend/test_error_handlers.py +2 -3
- udata/tests/frontend/test_hooks.py +5 -7
- udata/tests/frontend/test_markdown.py +3 -4
- udata/tests/helpers.py +2 -7
- udata/tests/metrics/test_metrics.py +52 -48
- udata/tests/metrics/test_tasks.py +154 -150
- udata/tests/organization/test_csv_adapter.py +2 -5
- udata/tests/organization/test_notifications.py +2 -4
- udata/tests/organization/test_organization_model.py +3 -4
- udata/tests/organization/test_organization_rdf.py +2 -8
- udata/tests/plugin.py +6 -110
- udata/tests/reuse/test_reuse_model.py +3 -4
- udata/tests/site/test_site_api.py +0 -2
- udata/tests/site/test_site_csv_exports.py +0 -2
- udata/tests/site/test_site_metrics.py +2 -4
- udata/tests/site/test_site_model.py +2 -2
- udata/tests/site/test_site_rdf.py +4 -7
- udata/tests/test_activity.py +3 -3
- udata/tests/test_api_fields.py +6 -9
- udata/tests/test_cors.py +0 -2
- udata/tests/test_dcat_commands.py +2 -3
- udata/tests/test_discussions.py +2 -7
- udata/tests/test_mail.py +150 -114
- udata/tests/test_migrations.py +413 -419
- udata/tests/test_model.py +10 -11
- udata/tests/test_notifications.py +2 -3
- udata/tests/test_owned.py +3 -3
- udata/tests/test_rdf.py +19 -15
- udata/tests/test_routing.py +5 -5
- udata/tests/test_storages.py +6 -5
- udata/tests/test_tags.py +2 -4
- udata/tests/test_topics.py +2 -4
- udata/tests/test_transfer.py +4 -5
- udata/tests/topic/test_topic_tasks.py +25 -27
- udata/tests/user/test_user_rdf.py +2 -8
- udata/tests/user/test_user_tasks.py +3 -5
- udata/tests/workers/test_jobs_commands.py +2 -2
- udata/tests/workers/test_tasks_routing.py +27 -27
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +369 -435
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +371 -437
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +369 -435
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +381 -447
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +371 -437
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +371 -437
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +372 -438
- udata/translations/udata.pot +379 -440
- udata/utils.py +14 -2
- {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/METADATA +1 -2
- {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/RECORD +205 -242
- udata/templates/mail/account_deleted.html +0 -5
- udata/templates/mail/account_deleted.txt +0 -6
- udata/templates/mail/account_inactivity.html +0 -40
- udata/templates/mail/account_inactivity.txt +0 -31
- udata/templates/mail/badge_added_association.html +0 -33
- udata/templates/mail/badge_added_association.txt +0 -11
- udata/templates/mail/badge_added_certified.html +0 -33
- udata/templates/mail/badge_added_certified.txt +0 -11
- udata/templates/mail/badge_added_company.html +0 -33
- udata/templates/mail/badge_added_company.txt +0 -11
- udata/templates/mail/badge_added_local_authority.html +0 -33
- udata/templates/mail/badge_added_local_authority.txt +0 -11
- udata/templates/mail/badge_added_public_service.html +0 -33
- udata/templates/mail/badge_added_public_service.txt +0 -11
- udata/templates/mail/discussion_closed.html +0 -47
- udata/templates/mail/discussion_closed.txt +0 -16
- udata/templates/mail/inactive_account_deleted.html +0 -5
- udata/templates/mail/inactive_account_deleted.txt +0 -6
- udata/templates/mail/membership_refused.html +0 -20
- udata/templates/mail/membership_refused.txt +0 -11
- udata/templates/mail/membership_request.html +0 -46
- udata/templates/mail/membership_request.txt +0 -12
- udata/templates/mail/new_discussion.html +0 -44
- udata/templates/mail/new_discussion.txt +0 -15
- udata/templates/mail/new_discussion_comment.html +0 -45
- udata/templates/mail/new_discussion_comment.txt +0 -16
- udata/templates/mail/new_member.html +0 -27
- udata/templates/mail/new_member.txt +0 -11
- udata/templates/mail/new_reuse.html +0 -37
- udata/templates/mail/new_reuse.txt +0 -9
- udata/templates/mail/test.html +0 -6
- udata/templates/mail/test.txt +0 -6
- udata/templates/mail/user_mail_card.html +0 -26
- udata/templates/security/email/base.html +0 -105
- udata/templates/security/email/base.txt +0 -6
- udata/templates/security/email/button.html +0 -3
- udata/templates/security/email/change_notice.html +0 -22
- udata/templates/security/email/change_notice.txt +0 -8
- udata/templates/security/email/confirmation_instructions.html +0 -20
- udata/templates/security/email/confirmation_instructions.txt +0 -7
- udata/templates/security/email/login_instructions.html +0 -19
- udata/templates/security/email/login_instructions.txt +0 -7
- udata/templates/security/email/reset_instructions.html +0 -24
- udata/templates/security/email/reset_instructions.txt +0 -9
- udata/templates/security/email/reset_notice.html +0 -11
- udata/templates/security/email/reset_notice.txt +0 -4
- udata/templates/security/email/welcome.html +0 -24
- udata/templates/security/email/welcome.txt +0 -9
- udata/templates/security/email/welcome_existing.html +0 -32
- udata/templates/security/email/welcome_existing.txt +0 -14
- udata/terms.md +0 -6
- udata/tests/frontend/__init__.py +0 -23
- udata/tests/metrics/conftest.py +0 -15
- {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/WHEEL +0 -0
- {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/entry_points.txt +0 -0
- {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/licenses/LICENSE +0 -0
- {udata-12.0.2.dev15.dist-info → udata-13.0.1.dev21.dist-info}/top_level.txt +0 -0
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from tempfile import NamedTemporaryFile
|
|
2
2
|
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
3
|
from udata.core.organization.constants import CERTIFIED, PUBLIC_SERVICE
|
|
6
4
|
from udata.core.organization.factories import OrganizationFactory
|
|
5
|
+
from udata.tests.api import PytestOnlyDBTestCase
|
|
7
6
|
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
class BadgeCommandTest:
|
|
8
|
+
class BadgeCommandTest(PytestOnlyDBTestCase):
|
|
11
9
|
def toggle(self, path_or_id, kind):
|
|
12
10
|
return self.cli("badges", "toggle", path_or_id, kind)
|
|
13
11
|
|
|
@@ -2,7 +2,7 @@ from udata.api_fields import field
|
|
|
2
2
|
from udata.auth import login_user
|
|
3
3
|
from udata.core.user.factories import UserFactory
|
|
4
4
|
from udata.mongo import db
|
|
5
|
-
from udata.tests import
|
|
5
|
+
from udata.tests.api import DBTestCase
|
|
6
6
|
|
|
7
7
|
from ..models import Badge, BadgeMixin, BadgesList
|
|
8
8
|
|
|
@@ -33,7 +33,7 @@ class Fake(db.Document, FakeBadgeMixin):
|
|
|
33
33
|
pass
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class BadgeMixinTest(
|
|
36
|
+
class BadgeMixinTest(DBTestCase):
|
|
37
37
|
def test_attributes(self):
|
|
38
38
|
"""It should have a badge list"""
|
|
39
39
|
fake = Fake.objects.create()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import udata.core.dataservices.tasks # noqa
|
|
2
|
+
import udata.core.dataset.tasks # noqa
|
|
3
|
+
from udata.core.badges.tasks import update_badges
|
|
4
|
+
from udata.core.constants import HVD
|
|
5
|
+
from udata.core.dataservices.factories import DataserviceFactory
|
|
6
|
+
from udata.core.dataservices.models import Dataservice
|
|
7
|
+
from udata.core.dataset.factories import DatasetFactory
|
|
8
|
+
from udata.core.dataset.models import Dataset
|
|
9
|
+
from udata.core.organization.constants import CERTIFIED, PUBLIC_SERVICE
|
|
10
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
11
|
+
from udata.tests.api import DBTestCase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BadgeTasksTest(DBTestCase):
|
|
15
|
+
def test_update_badges(self):
|
|
16
|
+
"""
|
|
17
|
+
Test update_badges run the appropriate badge update jobs.
|
|
18
|
+
In particular, test that the two following registered jobs run and work as expected:
|
|
19
|
+
- update_dataset_hvd_badge
|
|
20
|
+
- update_dataservice_hvd_badge
|
|
21
|
+
"""
|
|
22
|
+
org = OrganizationFactory()
|
|
23
|
+
org.add_badge(PUBLIC_SERVICE)
|
|
24
|
+
org.add_badge(CERTIFIED)
|
|
25
|
+
|
|
26
|
+
datasets = [
|
|
27
|
+
DatasetFactory(organization=org, tags=["hvd"]), # Should be badged HVD
|
|
28
|
+
DatasetFactory(organization=org, tags=["random"]), # Should not be badged HVD
|
|
29
|
+
DatasetFactory(
|
|
30
|
+
organization=org,
|
|
31
|
+
tags=[],
|
|
32
|
+
badges=[Dataset.badges.field.document_type(kind=HVD)],
|
|
33
|
+
), # Badge should be remove
|
|
34
|
+
]
|
|
35
|
+
dataservices = [
|
|
36
|
+
DataserviceFactory(organization=org, tags=["hvd"]), # Should be badged HVD
|
|
37
|
+
DataserviceFactory(organization=org, tags=["random"]), # Should not be badged HVD
|
|
38
|
+
DataserviceFactory(
|
|
39
|
+
organization=org,
|
|
40
|
+
tags=[],
|
|
41
|
+
badges=[Dataservice.badges.field.document_type(kind=HVD)],
|
|
42
|
+
), # Badge should be remove
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
update_badges.run()
|
|
46
|
+
|
|
47
|
+
[model.reload() for model in (*datasets, *dataservices)]
|
|
48
|
+
|
|
49
|
+
assert datasets[0].badges[0].kind == HVD
|
|
50
|
+
assert datasets[1].badges == []
|
|
51
|
+
assert datasets[2].badges == []
|
|
52
|
+
|
|
53
|
+
assert dataservices[0].badges[0].kind == HVD
|
|
54
|
+
assert dataservices[1].badges == []
|
|
55
|
+
assert dataservices[2].badges == []
|
udata/core/constants.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
HVD = "hvd"
|
|
@@ -9,6 +9,14 @@ CONTACT_ROLES = {
|
|
|
9
9
|
"contact": _("Contact"),
|
|
10
10
|
"creator": _("Creator"),
|
|
11
11
|
"publisher": _("Publisher"),
|
|
12
|
+
"rightsHolder": _("Rights Holder"),
|
|
13
|
+
"custodian": _("Custodian"),
|
|
14
|
+
"distributor": _("Distributor"),
|
|
15
|
+
"originator": _("Originator"),
|
|
16
|
+
"principalInvestigator": _("Principal Investigator"),
|
|
17
|
+
"processor": _("Processor"),
|
|
18
|
+
"resourceProvider": _("Resource Provider"),
|
|
19
|
+
"user": _("User"),
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
|
udata/core/dataservices/api.py
CHANGED
|
@@ -9,7 +9,7 @@ from flask_login import current_user
|
|
|
9
9
|
from udata.api import API, api, fields
|
|
10
10
|
from udata.api_fields import patch
|
|
11
11
|
from udata.auth import admin_permission
|
|
12
|
-
from udata.core.
|
|
12
|
+
from udata.core.access_type.constants import AccessType
|
|
13
13
|
from udata.core.dataset.models import Dataset
|
|
14
14
|
from udata.core.followers.api import FollowAPI
|
|
15
15
|
from udata.frontend.markdown import md
|
|
@@ -48,7 +48,7 @@ class DataservicesAPI(API):
|
|
|
48
48
|
dataservice = patch(Dataservice(), request)
|
|
49
49
|
if not dataservice.owner and not dataservice.organization:
|
|
50
50
|
dataservice.owner = current_user._get_current_object()
|
|
51
|
-
if dataservice.access_type !=
|
|
51
|
+
if dataservice.access_type != AccessType.RESTRICTED:
|
|
52
52
|
dataservice.access_audiences = []
|
|
53
53
|
dataservice.save()
|
|
54
54
|
return dataservice, 201
|
|
@@ -115,7 +115,7 @@ class DataserviceAPI(API):
|
|
|
115
115
|
|
|
116
116
|
patch(dataservice, request)
|
|
117
117
|
dataservice.metadata_modified_at = datetime.utcnow()
|
|
118
|
-
if dataservice.access_type !=
|
|
118
|
+
if dataservice.access_type != AccessType.RESTRICTED:
|
|
119
119
|
dataservice.access_audiences = []
|
|
120
120
|
|
|
121
121
|
dataservice.save()
|
udata/core/dataservices/apiv2.py
CHANGED
|
@@ -2,7 +2,8 @@ from flask import request
|
|
|
2
2
|
|
|
3
3
|
from udata import search
|
|
4
4
|
from udata.api import API, apiv2
|
|
5
|
-
from udata.core.
|
|
5
|
+
from udata.core.access_type.models import AccessAudience
|
|
6
|
+
from udata.core.dataservices.models import Dataservice, HarvestMetadata
|
|
6
7
|
from udata.utils import multi_to_dict
|
|
7
8
|
|
|
8
9
|
from .models import dataservice_permissions_fields
|
|
@@ -11,6 +12,7 @@ from .search import DataserviceSearch
|
|
|
11
12
|
apiv2.inherit("DataservicePermissions", dataservice_permissions_fields)
|
|
12
13
|
apiv2.inherit("DataservicePage", Dataservice.__page_fields__)
|
|
13
14
|
apiv2.inherit("Dataservice (read)", Dataservice.__read_fields__)
|
|
15
|
+
apiv2.inherit("DataserviceReference", Dataservice.__ref_fields__)
|
|
14
16
|
apiv2.inherit("HarvestMetadata (read)", HarvestMetadata.__read_fields__)
|
|
15
17
|
apiv2.inherit("AccessAudience (read)", AccessAudience.__read_fields__)
|
|
16
18
|
|
|
@@ -1,30 +1 @@
|
|
|
1
1
|
DATASERVICE_FORMATS = ["REST", "WMS", "WSL"]
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
DATASERVICE_ACCESS_TYPE_OPEN = "open"
|
|
5
|
-
DATASERVICE_ACCESS_TYPE_OPEN_WITH_ACCOUNT = "open_with_account"
|
|
6
|
-
DATASERVICE_ACCESS_TYPE_RESTRICTED = "restricted"
|
|
7
|
-
DATASERVICE_ACCESS_TYPES = [
|
|
8
|
-
DATASERVICE_ACCESS_TYPE_OPEN,
|
|
9
|
-
DATASERVICE_ACCESS_TYPE_OPEN_WITH_ACCOUNT,
|
|
10
|
-
DATASERVICE_ACCESS_TYPE_RESTRICTED,
|
|
11
|
-
]
|
|
12
|
-
|
|
13
|
-
DATASERVICE_ACCESS_AUDIENCE_ADMINISTRATION = "local_authority_and_administration"
|
|
14
|
-
DATASERVICE_ACCESS_AUDIENCE_COMPANY = "company_and_association"
|
|
15
|
-
DATASERVICE_ACCESS_AUDIENCE_PRIVATE = "private"
|
|
16
|
-
|
|
17
|
-
DATASERVICE_ACCESS_AUDIENCE_TYPES = [
|
|
18
|
-
DATASERVICE_ACCESS_AUDIENCE_ADMINISTRATION,
|
|
19
|
-
DATASERVICE_ACCESS_AUDIENCE_COMPANY,
|
|
20
|
-
DATASERVICE_ACCESS_AUDIENCE_PRIVATE,
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
DATASERVICE_ACCESS_AUDIENCE_YES = "yes"
|
|
24
|
-
DATASERVICE_ACCESS_AUDIENCE_NO = "no"
|
|
25
|
-
DATASERVICE_ACCESS_AUDIENCE_UNDER_CONDITIONS = "under_condition"
|
|
26
|
-
DATASERVICE_ACCESS_AUDIENCE_CONDITIONS = [
|
|
27
|
-
DATASERVICE_ACCESS_AUDIENCE_YES,
|
|
28
|
-
DATASERVICE_ACCESS_AUDIENCE_NO,
|
|
29
|
-
DATASERVICE_ACCESS_AUDIENCE_UNDER_CONDITIONS,
|
|
30
|
-
]
|
|
@@ -2,19 +2,17 @@ from datetime import datetime
|
|
|
2
2
|
|
|
3
3
|
from blinker import Signal
|
|
4
4
|
from flask import url_for
|
|
5
|
+
from flask_babel import LazyString
|
|
5
6
|
from mongoengine import Q
|
|
6
7
|
from mongoengine.signals import post_save
|
|
7
8
|
|
|
8
9
|
import udata.core.contact_point.api_fields as contact_api_fields
|
|
9
10
|
from udata.api import api, fields
|
|
10
11
|
from udata.api_fields import field, generate_fields
|
|
12
|
+
from udata.core.access_type.models import WithAccessType
|
|
11
13
|
from udata.core.activity.models import Auditable
|
|
12
|
-
from udata.core.
|
|
13
|
-
|
|
14
|
-
DATASERVICE_ACCESS_AUDIENCE_TYPES,
|
|
15
|
-
DATASERVICE_ACCESS_TYPES,
|
|
16
|
-
DATASERVICE_FORMATS,
|
|
17
|
-
)
|
|
14
|
+
from udata.core.constants import HVD
|
|
15
|
+
from udata.core.dataservices.constants import DATASERVICE_FORMATS
|
|
18
16
|
from udata.core.dataset.api_fields import dataset_ref_fields
|
|
19
17
|
from udata.core.dataset.models import Dataset
|
|
20
18
|
from udata.core.linkable import Linkable
|
|
@@ -22,19 +20,12 @@ from udata.core.metrics.helpers import get_stock_metrics
|
|
|
22
20
|
from udata.core.metrics.models import WithMetrics
|
|
23
21
|
from udata.core.owned import Owned, OwnedQuerySet
|
|
24
22
|
from udata.i18n import lazy_gettext as _
|
|
25
|
-
from udata.models import Discussion, Follow, db
|
|
26
|
-
from udata.mongo.errors import FieldValidationError
|
|
23
|
+
from udata.models import Badge, BadgeMixin, BadgesList, Discussion, Follow, db
|
|
27
24
|
from udata.uris import cdata_url
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# "page"
|
|
33
|
-
# "quality" # Peut-être pas dans une v1 car la qualité sera probablement calculé différemment
|
|
34
|
-
# "datasets" # objet : liste de datasets liés à une API
|
|
35
|
-
# "spatial"
|
|
36
|
-
# "temporal_coverage"
|
|
37
|
-
|
|
26
|
+
BADGES: dict[str, LazyString] = {
|
|
27
|
+
HVD: _("Dataservice serving high value datasets"),
|
|
28
|
+
}
|
|
38
29
|
|
|
39
30
|
dataservice_permissions_fields = api.model(
|
|
40
31
|
"DataservicePermissions",
|
|
@@ -89,6 +80,20 @@ class DataserviceQuerySet(OwnedQuerySet):
|
|
|
89
80
|
return self(dataservices_filter)
|
|
90
81
|
|
|
91
82
|
|
|
83
|
+
def validate_badge(value):
|
|
84
|
+
if value not in Dataservice.__badges__.keys():
|
|
85
|
+
raise db.ValidationError("Unknown badge type")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class DataserviceBadge(Badge):
|
|
89
|
+
kind = db.StringField(required=True, validation=validate_badge)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class DataserviceBadgeMixin(BadgeMixin):
|
|
93
|
+
badges = field(BadgesList(DataserviceBadge), **BadgeMixin.default_badges_list_params)
|
|
94
|
+
__badges__ = BADGES
|
|
95
|
+
|
|
96
|
+
|
|
92
97
|
@generate_fields()
|
|
93
98
|
class HarvestMetadata(db.EmbeddedDocument):
|
|
94
99
|
backend = field(db.StringField())
|
|
@@ -119,28 +124,13 @@ class HarvestMetadata(db.EmbeddedDocument):
|
|
|
119
124
|
archived_reason = field(db.StringField())
|
|
120
125
|
|
|
121
126
|
|
|
122
|
-
@generate_fields()
|
|
123
|
-
class AccessAudience(db.EmbeddedDocument):
|
|
124
|
-
role = field(db.StringField(choices=DATASERVICE_ACCESS_AUDIENCE_TYPES), filterable={})
|
|
125
|
-
condition = field(db.StringField(choices=DATASERVICE_ACCESS_AUDIENCE_CONDITIONS), filterable={})
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def check_only_one_condition_per_role(access_audiences, **_kwargs):
|
|
129
|
-
roles = set(e["role"] for e in access_audiences)
|
|
130
|
-
if len(roles) != len(access_audiences):
|
|
131
|
-
raise FieldValidationError(
|
|
132
|
-
_("You can only set one condition for a given access audience role"),
|
|
133
|
-
field="access_audiences",
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
|
|
137
127
|
def filter_by_topic(base_query, filter_value):
|
|
138
128
|
from udata.core.topic.models import Topic
|
|
139
129
|
|
|
140
130
|
try:
|
|
141
131
|
topic = Topic.objects.get(id=filter_value)
|
|
142
132
|
except Topic.DoesNotExist:
|
|
143
|
-
|
|
133
|
+
return base_query
|
|
144
134
|
else:
|
|
145
135
|
return base_query.filter(
|
|
146
136
|
id__in=[
|
|
@@ -150,18 +140,32 @@ def filter_by_topic(base_query, filter_value):
|
|
|
150
140
|
)
|
|
151
141
|
|
|
152
142
|
|
|
143
|
+
def filter_by_reuse(base_query, filter_value):
|
|
144
|
+
from udata.core.reuse.models import Reuse
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
reuse = Reuse.objects.get(id=filter_value)
|
|
148
|
+
except Reuse.DoesNotExist:
|
|
149
|
+
return base_query
|
|
150
|
+
else:
|
|
151
|
+
return base_query.filter(id__in=[dataservice.id for dataservice in reuse.dataservices])
|
|
152
|
+
|
|
153
|
+
|
|
153
154
|
@generate_fields(
|
|
154
155
|
searchable=True,
|
|
155
156
|
nested_filters={"organization_badge": "organization.badges"},
|
|
156
157
|
standalone_filters=[
|
|
157
|
-
{"key": "topic", "constraints": "objectid", "query": filter_by_topic, "type": str}
|
|
158
|
+
{"key": "topic", "constraints": ["objectid"], "query": filter_by_topic, "type": str},
|
|
159
|
+
{"key": "reuse", "constraints": ["objectid"], "query": filter_by_reuse, "type": str},
|
|
158
160
|
],
|
|
159
161
|
additional_sorts=[
|
|
160
162
|
{"key": "followers", "value": "metrics.followers"},
|
|
161
163
|
{"key": "views", "value": "metrics.views"},
|
|
162
164
|
],
|
|
163
165
|
)
|
|
164
|
-
class Dataservice(
|
|
166
|
+
class Dataservice(
|
|
167
|
+
Auditable, WithMetrics, WithAccessType, DataserviceBadgeMixin, Linkable, Owned, db.Document
|
|
168
|
+
):
|
|
165
169
|
meta = {
|
|
166
170
|
"indexes": [
|
|
167
171
|
"$title",
|
|
@@ -212,14 +216,6 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
|
|
|
212
216
|
availability = field(db.FloatField(min=0, max=100), example="99.99")
|
|
213
217
|
availability_url = field(db.URLField())
|
|
214
218
|
|
|
215
|
-
access_type = field(db.StringField(choices=DATASERVICE_ACCESS_TYPES), filterable={})
|
|
216
|
-
access_audiences = field(
|
|
217
|
-
db.EmbeddedDocumentListField(AccessAudience),
|
|
218
|
-
checks=[check_only_one_condition_per_role],
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
authorization_request_url = field(db.URLField())
|
|
222
|
-
|
|
223
219
|
format = field(db.StringField(choices=DATASERVICE_FORMATS))
|
|
224
220
|
|
|
225
221
|
license = field(
|
|
@@ -303,7 +299,7 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
|
|
|
303
299
|
auditable=False,
|
|
304
300
|
)
|
|
305
301
|
|
|
306
|
-
@field(description="Link to the API endpoint for this dataservice")
|
|
302
|
+
@field(description="Link to the API endpoint for this dataservice", show_as_ref=True)
|
|
307
303
|
def self_api_url(self, **kwargs):
|
|
308
304
|
return url_for(
|
|
309
305
|
"api.dataservice",
|
|
@@ -323,6 +319,10 @@ class Dataservice(Auditable, WithMetrics, Linkable, Owned, db.Document):
|
|
|
323
319
|
"views",
|
|
324
320
|
]
|
|
325
321
|
|
|
322
|
+
@property
|
|
323
|
+
def is_visible(self):
|
|
324
|
+
return not self.is_hidden
|
|
325
|
+
|
|
326
326
|
@property
|
|
327
327
|
def is_hidden(self):
|
|
328
328
|
return self.private or self.deleted_at or self.archived_at
|
udata/core/dataservices/rdf.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from flask import current_app
|
|
2
2
|
from rdflib import RDF, BNode, Graph, Literal, URIRef
|
|
3
3
|
|
|
4
|
+
from udata.core.constants import HVD
|
|
4
5
|
from udata.core.dataservices.models import Dataservice
|
|
5
6
|
from udata.core.dataservices.models import HarvestMetadata as HarvestDataserviceMetadata
|
|
6
7
|
from udata.core.dataset.models import Dataset, License
|
|
@@ -146,7 +147,7 @@ def dataservice_to_rdf(dataservice: Dataservice, graph=None):
|
|
|
146
147
|
|
|
147
148
|
# Add DCAT-AP HVD properties if the dataservice is tagged hvd.
|
|
148
149
|
# See https://semiceu.github.io/DCAT-AP/releases/2.2.0-hvd/
|
|
149
|
-
is_hvd = current_app.config["HVD_SUPPORT"] and
|
|
150
|
+
is_hvd = current_app.config["HVD_SUPPORT"] and any(b.kind == HVD for b in dataservice.badges)
|
|
150
151
|
if is_hvd:
|
|
151
152
|
d.add(DCATAP.applicableLegislation, URIRef(HVD_LEGISLATION))
|
|
152
153
|
|
|
@@ -5,11 +5,7 @@ from flask_restx.inputs import boolean
|
|
|
5
5
|
|
|
6
6
|
from udata.api import api
|
|
7
7
|
from udata.api.parsers import ModelApiParser
|
|
8
|
-
from udata.core.
|
|
9
|
-
DATASERVICE_ACCESS_TYPE_OPEN,
|
|
10
|
-
DATASERVICE_ACCESS_TYPE_OPEN_WITH_ACCOUNT,
|
|
11
|
-
DATASERVICE_ACCESS_TYPE_RESTRICTED,
|
|
12
|
-
)
|
|
8
|
+
from udata.core.access_type.constants import AccessType
|
|
13
9
|
from udata.models import Dataservice, Organization, User
|
|
14
10
|
from udata.search import (
|
|
15
11
|
BoolFilter,
|
|
@@ -54,9 +50,9 @@ class DataserviceApiParser(ModelApiParser):
|
|
|
54
50
|
dataservices = dataservices.filter(organization=args["organization"])
|
|
55
51
|
if "is_restricted" in args:
|
|
56
52
|
dataservices = dataservices.filter(
|
|
57
|
-
access_type__in=[
|
|
53
|
+
access_type__in=[AccessType.RESTRICTED]
|
|
58
54
|
if boolean(args["is_restricted"])
|
|
59
|
-
else [
|
|
55
|
+
else [AccessType.OPEN, AccessType.OPEN_WITH_ACCOUNT]
|
|
60
56
|
)
|
|
61
57
|
if args.get("featured"):
|
|
62
58
|
dataservices = dataservices.filter(featured=args["featured"])
|
|
@@ -79,7 +75,7 @@ class DataserviceSearch(ModelSearchAdapter):
|
|
|
79
75
|
|
|
80
76
|
@classmethod
|
|
81
77
|
def is_indexable(cls, dataservice: Dataservice) -> bool:
|
|
82
|
-
return dataservice.
|
|
78
|
+
return dataservice.is_visible
|
|
83
79
|
|
|
84
80
|
@classmethod
|
|
85
81
|
def mongo_search(cls, args):
|
|
@@ -126,6 +122,6 @@ class DataserviceSearch(ModelSearchAdapter):
|
|
|
126
122
|
"tags": dataservice.tags,
|
|
127
123
|
"extras": extras,
|
|
128
124
|
"followers": dataservice.metrics.get("followers", 0),
|
|
129
|
-
"is_restricted": dataservice.access_type ==
|
|
125
|
+
"is_restricted": dataservice.access_type == AccessType.RESTRICTED,
|
|
130
126
|
"views": dataservice.metrics.get("views", 0),
|
|
131
127
|
}
|
udata/core/dataservices/tasks.py
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
from celery.utils.log import get_task_logger
|
|
2
|
+
from flask import current_app
|
|
2
3
|
|
|
4
|
+
from udata.core.badges import tasks as badge_tasks
|
|
5
|
+
from udata.core.constants import HVD
|
|
3
6
|
from udata.core.dataservices.models import Dataservice
|
|
7
|
+
from udata.core.organization.constants import CERTIFIED, PUBLIC_SERVICE
|
|
8
|
+
from udata.core.organization.models import Organization
|
|
4
9
|
from udata.core.topic.models import TopicElement
|
|
5
10
|
from udata.harvest.models import HarvestJob
|
|
6
11
|
from udata.models import Discussion, Follow, Transfer
|
|
@@ -25,3 +30,31 @@ def purge_dataservices(self):
|
|
|
25
30
|
TopicElement.objects(element=dataservice).update(element=None)
|
|
26
31
|
# Remove dataservice
|
|
27
32
|
dataservice.delete()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@badge_tasks.register(model=Dataservice, badge=HVD)
|
|
36
|
+
def update_dataservice_hvd_badge() -> None:
|
|
37
|
+
"""
|
|
38
|
+
Update HVD badges to candidate dataservices, based on the hvd tag.
|
|
39
|
+
Only dataservices owned by certified and public service organizations are candidate to have a HVD badge.
|
|
40
|
+
"""
|
|
41
|
+
if not current_app.config["HVD_SUPPORT"]:
|
|
42
|
+
log.error("You need to set HVD_SUPPORT if you want to update dataservice hvd badge")
|
|
43
|
+
return
|
|
44
|
+
public_certified_orgs = (
|
|
45
|
+
Organization.objects(badges__kind=PUBLIC_SERVICE).filter(badges__kind=CERTIFIED).only("id")
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
dataservices = Dataservice.objects(
|
|
49
|
+
tags="hvd", badges__kind__ne="hvd", organization__in=public_certified_orgs
|
|
50
|
+
)
|
|
51
|
+
log.info(f"Adding HVD badge to {dataservices.count()} dataservices")
|
|
52
|
+
for dataservice in dataservices:
|
|
53
|
+
dataservice.add_badge(HVD)
|
|
54
|
+
|
|
55
|
+
dataservices = Dataservice.objects(
|
|
56
|
+
tags__nin=["hvd"], badges__kind="hvd", organization__in=public_certified_orgs
|
|
57
|
+
)
|
|
58
|
+
log.info(f"Remove HVD badge from {dataservices.count()} dataservices")
|
|
59
|
+
for dataservice in dataservices:
|
|
60
|
+
dataservice.remove_badge(HVD)
|
udata/core/dataset/api_fields.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from udata.api import api, base_reference, fields
|
|
2
|
+
from udata.core.access_type.models import AccessAudience
|
|
2
3
|
from udata.core.badges.fields import badge_fields
|
|
3
4
|
from udata.core.contact_point.api_fields import contact_point_fields
|
|
4
5
|
from udata.core.organization.api_fields import org_ref_fields
|
|
@@ -287,6 +288,11 @@ DEFAULT_MASK = ",".join(
|
|
|
287
288
|
"temporal_coverage",
|
|
288
289
|
"spatial",
|
|
289
290
|
"license",
|
|
291
|
+
"access_type",
|
|
292
|
+
"access_audiences",
|
|
293
|
+
"authorization_request_url",
|
|
294
|
+
"access_type_reason_category",
|
|
295
|
+
"access_type_reason",
|
|
290
296
|
"uri",
|
|
291
297
|
"page",
|
|
292
298
|
"last_update",
|
|
@@ -394,6 +400,11 @@ dataset_fields = api.model(
|
|
|
394
400
|
"license": fields.String(
|
|
395
401
|
attribute="license.id", default=DEFAULT_LICENSE["id"], description="The dataset license"
|
|
396
402
|
),
|
|
403
|
+
"access_type": fields.String(allow_null=True),
|
|
404
|
+
"access_audiences": fields.List(fields.Nested(AccessAudience.__read_fields__)),
|
|
405
|
+
"authorization_request_url": fields.String(allow_null=True),
|
|
406
|
+
"access_type_reason_category": fields.String(allow_null=True),
|
|
407
|
+
"access_type_reason": fields.String(allow_null=True),
|
|
397
408
|
"uri": fields.String(
|
|
398
409
|
attribute=lambda d: d.self_api_url(),
|
|
399
410
|
description="The API URI for this dataset",
|
udata/core/dataset/apiv2.py
CHANGED
|
@@ -7,6 +7,7 @@ from flask_restx import marshal
|
|
|
7
7
|
|
|
8
8
|
from udata import search
|
|
9
9
|
from udata.api import API, apiv2, fields
|
|
10
|
+
from udata.core.access_type.models import AccessAudience
|
|
10
11
|
from udata.core.contact_point.api_fields import contact_point_fields
|
|
11
12
|
from udata.core.dataset.api_fields import license_fields
|
|
12
13
|
from udata.core.organization.api_fields import member_user_with_email_fields
|
|
@@ -62,6 +63,11 @@ DEFAULT_MASK_APIV2 = ",".join(
|
|
|
62
63
|
"temporal_coverage",
|
|
63
64
|
"spatial",
|
|
64
65
|
"license",
|
|
66
|
+
"access_type",
|
|
67
|
+
"access_audiences",
|
|
68
|
+
"access_type_reason_category",
|
|
69
|
+
"access_type_reason",
|
|
70
|
+
"authorization_request_url",
|
|
65
71
|
"uri",
|
|
66
72
|
"page",
|
|
67
73
|
"last_update",
|
|
@@ -202,6 +208,11 @@ dataset_fields = apiv2.model(
|
|
|
202
208
|
default=DEFAULT_LICENSE["id"],
|
|
203
209
|
description="The dataset license (full License object if `X-Get-Datasets-Full-Objects` is set, ID of the license otherwise)",
|
|
204
210
|
),
|
|
211
|
+
"access_type": fields.String(allow_null=True),
|
|
212
|
+
"access_audiences": fields.Nested(AccessAudience.__read_fields__),
|
|
213
|
+
"authorization_request_url": fields.String(allow_null=True),
|
|
214
|
+
"access_type_reason_category": fields.String(allow_null=True),
|
|
215
|
+
"access_type_reason": fields.String(allow_null=True),
|
|
205
216
|
"uri": fields.String(
|
|
206
217
|
attribute=lambda d: d.self_api_url(),
|
|
207
218
|
description="The API URI for this dataset",
|
udata/core/dataset/constants.py
CHANGED
udata/core/dataset/forms.py
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
from udata.core.access_type.constants import (
|
|
2
|
+
AccessAudienceCondition,
|
|
3
|
+
AccessAudienceType,
|
|
4
|
+
AccessType,
|
|
5
|
+
InspireLimitationCategory,
|
|
6
|
+
)
|
|
7
|
+
from udata.core.access_type.models import AccessAudience
|
|
1
8
|
from udata.core.spatial.forms import SpatialCoverageField
|
|
2
9
|
from udata.core.storages import resources
|
|
3
10
|
from udata.forms import ModelForm, fields, validators
|
|
@@ -116,6 +123,8 @@ class CommunityResourceForm(BaseResourceForm):
|
|
|
116
123
|
|
|
117
124
|
|
|
118
125
|
def unmarshal_frequency(form, field):
|
|
126
|
+
if field.data is None:
|
|
127
|
+
return
|
|
119
128
|
# We don't need to worry about invalid field.data being fed to UpdateFrequency here,
|
|
120
129
|
# since the API will already have ensured incoming data matches the field definition,
|
|
121
130
|
# which in our case is an enum of valid UpdateFrequency values.
|
|
@@ -139,6 +148,13 @@ def validate_contact_point(form, field):
|
|
|
139
148
|
)
|
|
140
149
|
|
|
141
150
|
|
|
151
|
+
class AccessAudienceForm(ModelForm):
|
|
152
|
+
model_class = AccessAudience
|
|
153
|
+
|
|
154
|
+
role = fields.SelectField(choices=[(e.value, e.value) for e in AccessAudienceType])
|
|
155
|
+
condition = fields.SelectField(choices=[(e.value, e.value) for e in AccessAudienceCondition])
|
|
156
|
+
|
|
157
|
+
|
|
142
158
|
class DatasetForm(ModelForm):
|
|
143
159
|
model_class = Dataset
|
|
144
160
|
|
|
@@ -157,6 +173,19 @@ class DatasetForm(ModelForm):
|
|
|
157
173
|
description=_("A short description of the dataset."),
|
|
158
174
|
)
|
|
159
175
|
license = fields.ModelSelectField(_("License"), model=License, allow_blank=True)
|
|
176
|
+
access_type = fields.SelectField(
|
|
177
|
+
choices=[(e.value, e.value) for e in AccessType],
|
|
178
|
+
default=AccessType.OPEN,
|
|
179
|
+
validators=[validators.optional()],
|
|
180
|
+
)
|
|
181
|
+
access_audiences = fields.NestedModelList(AccessAudienceForm)
|
|
182
|
+
authorization_request_url = fields.StringField(_("Authorization request URL"))
|
|
183
|
+
access_type_reason_category = fields.SelectField(
|
|
184
|
+
_("Access type reason category"),
|
|
185
|
+
choices=[(e.value, e.label) for e in InspireLimitationCategory],
|
|
186
|
+
validators=[validators.optional()],
|
|
187
|
+
)
|
|
188
|
+
access_type_reason = fields.StringField(_("Access type reason"))
|
|
160
189
|
frequency = fields.SelectField(
|
|
161
190
|
_("Update frequency"),
|
|
162
191
|
choices=list(UpdateFrequency),
|
udata/core/dataset/models.py
CHANGED
|
@@ -9,6 +9,7 @@ import Levenshtein
|
|
|
9
9
|
import requests
|
|
10
10
|
from blinker import signal
|
|
11
11
|
from flask import current_app, url_for
|
|
12
|
+
from flask_babel import LazyString
|
|
12
13
|
from mongoengine import ValidationError as MongoEngineValidationError
|
|
13
14
|
from mongoengine.fields import DateTimeField
|
|
14
15
|
from mongoengine.signals import post_save, pre_init, pre_save
|
|
@@ -17,7 +18,10 @@ from werkzeug.utils import cached_property
|
|
|
17
18
|
from udata.api_fields import field
|
|
18
19
|
from udata.app import cache
|
|
19
20
|
from udata.core import storages
|
|
21
|
+
from udata.core.access_type.constants import AccessType
|
|
22
|
+
from udata.core.access_type.models import WithAccessType, check_only_one_condition_per_role
|
|
20
23
|
from udata.core.activity.models import Auditable
|
|
24
|
+
from udata.core.constants import HVD
|
|
21
25
|
from udata.core.dataset.preview import TabularAPIPreview
|
|
22
26
|
from udata.core.linkable import Linkable
|
|
23
27
|
from udata.core.metrics.helpers import get_stock_metrics
|
|
@@ -35,7 +39,6 @@ from .constants import (
|
|
|
35
39
|
CLOSED_FORMATS,
|
|
36
40
|
DEFAULT_LICENSE,
|
|
37
41
|
DESCRIPTION_SHORT_SIZE_LIMIT,
|
|
38
|
-
HVD,
|
|
39
42
|
INSPIRE,
|
|
40
43
|
MAX_DISTANCE,
|
|
41
44
|
PIVOTAL_DATA,
|
|
@@ -62,7 +65,7 @@ __all__ = (
|
|
|
62
65
|
"ResourceSchema",
|
|
63
66
|
)
|
|
64
67
|
|
|
65
|
-
BADGES: dict[str,
|
|
68
|
+
BADGES: dict[str, LazyString] = {
|
|
66
69
|
PIVOTAL_DATA: _("Pivotal data"),
|
|
67
70
|
SPD: _("Reference data public service"),
|
|
68
71
|
INSPIRE: _("Inspire"),
|
|
@@ -530,7 +533,9 @@ class DatasetBadgeMixin(BadgeMixin):
|
|
|
530
533
|
__badges__ = BADGES
|
|
531
534
|
|
|
532
535
|
|
|
533
|
-
class Dataset(
|
|
536
|
+
class Dataset(
|
|
537
|
+
Auditable, WithMetrics, WithAccessType, DatasetBadgeMixin, Owned, Linkable, db.Document
|
|
538
|
+
):
|
|
534
539
|
title = field(db.StringField(required=True))
|
|
535
540
|
acronym = field(db.StringField(max_length=128))
|
|
536
541
|
# /!\ do not set directly the slug when creating or updating a dataset
|
|
@@ -681,6 +686,10 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
|
|
|
681
686
|
|
|
682
687
|
self.quality_cached = self.compute_quality()
|
|
683
688
|
|
|
689
|
+
check_only_one_condition_per_role(self.access_audiences)
|
|
690
|
+
if self.access_type and self.access_type != AccessType.OPEN:
|
|
691
|
+
self.license = None
|
|
692
|
+
|
|
684
693
|
for key, value in self.extras.items():
|
|
685
694
|
if not key.startswith("custom:"):
|
|
686
695
|
continue
|
|
@@ -780,10 +789,13 @@ class Dataset(Auditable, WithMetrics, DatasetBadgeMixin, Owned, Linkable, db.Doc
|
|
|
780
789
|
|
|
781
790
|
def compute_last_update(self):
|
|
782
791
|
"""
|
|
783
|
-
|
|
792
|
+
If dataset is harvested and its metadata contains a modified_at date, use it.
|
|
793
|
+
Else, use the more recent date we would have at the resource level (harvest, modified).
|
|
784
794
|
Default to dataset last_modified if no resource.
|
|
785
795
|
Resources should be fetched when calling this method.
|
|
786
796
|
"""
|
|
797
|
+
if self.harvest and self.harvest.modified_at:
|
|
798
|
+
return self.harvest.modified_at
|
|
787
799
|
if self.resources:
|
|
788
800
|
return max([res.last_modified for res in self.resources])
|
|
789
801
|
else:
|
udata/core/dataset/rdf.py
CHANGED
|
@@ -16,6 +16,7 @@ from rdflib.namespace import RDF
|
|
|
16
16
|
from rdflib.resource import Resource as RdfResource
|
|
17
17
|
|
|
18
18
|
from udata import i18n, uris
|
|
19
|
+
from udata.core.constants import HVD
|
|
19
20
|
from udata.core.dataset.models import HarvestDatasetMetadata, HarvestResourceMetadata
|
|
20
21
|
from udata.core.spatial.models import SpatialCoverage
|
|
21
22
|
from udata.harvest.exceptions import HarvestSkipException
|
|
@@ -330,7 +331,7 @@ def dataset_to_rdf(dataset: Dataset, graph: Graph | None = None) -> RdfResource:
|
|
|
330
331
|
|
|
331
332
|
# Add DCAT-AP HVD properties if the dataset is tagged hvd.
|
|
332
333
|
# See https://semiceu.github.io/DCAT-AP/releases/2.2.0-hvd/
|
|
333
|
-
is_hvd = current_app.config["HVD_SUPPORT"] and
|
|
334
|
+
is_hvd = current_app.config["HVD_SUPPORT"] and any(b.kind == HVD for b in dataset.badges)
|
|
334
335
|
if is_hvd:
|
|
335
336
|
d.add(DCATAP.applicableLegislation, URIRef(HVD_LEGISLATION))
|
|
336
337
|
|