udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30454__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.
- tasks/__init__.py +109 -107
- tasks/helpers.py +18 -18
- udata/__init__.py +4 -4
- udata/admin/views.py +5 -5
- udata/api/__init__.py +111 -134
- udata/api/commands.py +45 -37
- udata/api/errors.py +5 -4
- udata/api/fields.py +23 -21
- udata/api/oauth2.py +55 -74
- udata/api/parsers.py +15 -15
- udata/api/signals.py +1 -1
- udata/api_fields.py +137 -89
- udata/app.py +58 -55
- udata/assets.py +5 -5
- udata/auth/__init__.py +37 -26
- udata/auth/forms.py +23 -15
- udata/auth/helpers.py +1 -1
- udata/auth/mails.py +3 -3
- udata/auth/password_validation.py +19 -15
- udata/auth/views.py +94 -68
- udata/commands/__init__.py +71 -69
- udata/commands/cache.py +7 -7
- udata/commands/db.py +201 -140
- udata/commands/dcat.py +36 -30
- udata/commands/fixtures.py +100 -84
- udata/commands/images.py +21 -20
- udata/commands/info.py +17 -20
- udata/commands/init.py +10 -10
- udata/commands/purge.py +12 -13
- udata/commands/serve.py +41 -29
- udata/commands/static.py +16 -18
- udata/commands/test.py +20 -20
- udata/commands/tests/fixtures.py +26 -24
- udata/commands/worker.py +31 -33
- udata/core/__init__.py +12 -12
- udata/core/activity/__init__.py +0 -1
- udata/core/activity/api.py +59 -49
- udata/core/activity/models.py +28 -26
- udata/core/activity/signals.py +1 -1
- udata/core/activity/tasks.py +16 -10
- udata/core/badges/api.py +6 -6
- udata/core/badges/commands.py +14 -13
- udata/core/badges/fields.py +8 -5
- udata/core/badges/forms.py +7 -4
- udata/core/badges/models.py +16 -31
- udata/core/badges/permissions.py +1 -3
- udata/core/badges/signals.py +2 -2
- udata/core/badges/tasks.py +3 -2
- udata/core/badges/tests/test_commands.py +10 -10
- udata/core/badges/tests/test_model.py +24 -31
- udata/core/contact_point/api.py +19 -18
- udata/core/contact_point/api_fields.py +21 -14
- udata/core/contact_point/factories.py +2 -2
- udata/core/contact_point/forms.py +7 -6
- udata/core/contact_point/models.py +3 -5
- udata/core/dataservices/api.py +26 -21
- udata/core/dataservices/factories.py +13 -11
- udata/core/dataservices/models.py +35 -40
- udata/core/dataservices/permissions.py +4 -4
- udata/core/dataservices/rdf.py +40 -17
- udata/core/dataservices/tasks.py +4 -3
- udata/core/dataset/actions.py +10 -10
- udata/core/dataset/activities.py +21 -23
- udata/core/dataset/api.py +321 -298
- udata/core/dataset/api_fields.py +443 -271
- udata/core/dataset/apiv2.py +305 -229
- udata/core/dataset/commands.py +38 -36
- udata/core/dataset/constants.py +61 -54
- udata/core/dataset/csv.py +70 -74
- udata/core/dataset/events.py +39 -32
- udata/core/dataset/exceptions.py +8 -4
- udata/core/dataset/factories.py +57 -65
- udata/core/dataset/forms.py +87 -63
- udata/core/dataset/models.py +336 -280
- udata/core/dataset/permissions.py +9 -6
- udata/core/dataset/preview.py +15 -17
- udata/core/dataset/rdf.py +156 -122
- udata/core/dataset/search.py +92 -77
- udata/core/dataset/signals.py +1 -1
- udata/core/dataset/tasks.py +63 -54
- udata/core/discussions/actions.py +5 -5
- udata/core/discussions/api.py +124 -120
- udata/core/discussions/factories.py +2 -2
- udata/core/discussions/forms.py +9 -7
- udata/core/discussions/metrics.py +1 -3
- udata/core/discussions/models.py +25 -24
- udata/core/discussions/notifications.py +18 -14
- udata/core/discussions/permissions.py +3 -3
- udata/core/discussions/signals.py +4 -4
- udata/core/discussions/tasks.py +24 -28
- udata/core/followers/api.py +32 -33
- udata/core/followers/models.py +9 -9
- udata/core/followers/signals.py +3 -3
- udata/core/jobs/actions.py +7 -7
- udata/core/jobs/api.py +99 -92
- udata/core/jobs/commands.py +48 -49
- udata/core/jobs/forms.py +11 -11
- udata/core/jobs/models.py +6 -6
- udata/core/metrics/__init__.py +2 -2
- udata/core/metrics/commands.py +34 -30
- udata/core/metrics/models.py +2 -4
- udata/core/metrics/signals.py +1 -1
- udata/core/metrics/tasks.py +3 -3
- udata/core/organization/activities.py +12 -15
- udata/core/organization/api.py +167 -174
- udata/core/organization/api_fields.py +183 -124
- udata/core/organization/apiv2.py +32 -32
- udata/core/organization/commands.py +20 -22
- udata/core/organization/constants.py +11 -11
- udata/core/organization/csv.py +17 -15
- udata/core/organization/factories.py +8 -11
- udata/core/organization/forms.py +32 -26
- udata/core/organization/metrics.py +2 -1
- udata/core/organization/models.py +87 -67
- udata/core/organization/notifications.py +18 -14
- udata/core/organization/permissions.py +10 -11
- udata/core/organization/rdf.py +14 -14
- udata/core/organization/search.py +30 -28
- udata/core/organization/signals.py +7 -7
- udata/core/organization/tasks.py +42 -61
- udata/core/owned.py +38 -27
- udata/core/post/api.py +82 -81
- udata/core/post/constants.py +8 -5
- udata/core/post/factories.py +4 -4
- udata/core/post/forms.py +13 -14
- udata/core/post/models.py +20 -22
- udata/core/post/tests/test_api.py +30 -32
- udata/core/reports/api.py +8 -7
- udata/core/reports/constants.py +1 -3
- udata/core/reports/models.py +10 -10
- udata/core/reuse/activities.py +15 -19
- udata/core/reuse/api.py +123 -126
- udata/core/reuse/api_fields.py +120 -85
- udata/core/reuse/apiv2.py +11 -10
- udata/core/reuse/constants.py +23 -23
- udata/core/reuse/csv.py +18 -18
- udata/core/reuse/factories.py +5 -9
- udata/core/reuse/forms.py +24 -21
- udata/core/reuse/models.py +55 -51
- udata/core/reuse/permissions.py +2 -2
- udata/core/reuse/search.py +49 -46
- udata/core/reuse/signals.py +1 -1
- udata/core/reuse/tasks.py +4 -5
- udata/core/site/api.py +47 -50
- udata/core/site/factories.py +2 -2
- udata/core/site/forms.py +4 -5
- udata/core/site/models.py +94 -63
- udata/core/site/rdf.py +14 -14
- udata/core/spam/api.py +16 -9
- udata/core/spam/constants.py +4 -4
- udata/core/spam/fields.py +13 -7
- udata/core/spam/models.py +27 -20
- udata/core/spam/signals.py +1 -1
- udata/core/spam/tests/test_spam.py +6 -5
- udata/core/spatial/api.py +72 -80
- udata/core/spatial/api_fields.py +73 -58
- udata/core/spatial/commands.py +67 -64
- udata/core/spatial/constants.py +3 -3
- udata/core/spatial/factories.py +37 -54
- udata/core/spatial/forms.py +27 -26
- udata/core/spatial/geoids.py +17 -17
- udata/core/spatial/models.py +43 -47
- udata/core/spatial/tasks.py +2 -1
- udata/core/spatial/tests/test_api.py +115 -130
- udata/core/spatial/tests/test_fields.py +74 -77
- udata/core/spatial/tests/test_geoid.py +22 -22
- udata/core/spatial/tests/test_models.py +5 -7
- udata/core/spatial/translations.py +16 -16
- udata/core/storages/__init__.py +16 -18
- udata/core/storages/api.py +66 -64
- udata/core/storages/tasks.py +7 -7
- udata/core/storages/utils.py +15 -15
- udata/core/storages/views.py +5 -6
- udata/core/tags/api.py +17 -14
- udata/core/tags/csv.py +4 -4
- udata/core/tags/models.py +8 -5
- udata/core/tags/tasks.py +11 -13
- udata/core/tags/views.py +4 -4
- udata/core/topic/api.py +84 -73
- udata/core/topic/apiv2.py +157 -127
- udata/core/topic/factories.py +3 -4
- udata/core/topic/forms.py +12 -14
- udata/core/topic/models.py +14 -19
- udata/core/topic/parsers.py +26 -26
- udata/core/user/activities.py +30 -29
- udata/core/user/api.py +151 -152
- udata/core/user/api_fields.py +132 -100
- udata/core/user/apiv2.py +7 -7
- udata/core/user/commands.py +38 -38
- udata/core/user/factories.py +8 -9
- udata/core/user/forms.py +14 -11
- udata/core/user/metrics.py +2 -2
- udata/core/user/models.py +68 -69
- udata/core/user/permissions.py +4 -5
- udata/core/user/rdf.py +7 -8
- udata/core/user/tasks.py +2 -2
- udata/core/user/tests/test_user_model.py +24 -16
- udata/cors.py +99 -0
- udata/db/tasks.py +2 -1
- udata/entrypoints.py +35 -31
- udata/errors.py +2 -1
- udata/event/values.py +6 -6
- udata/factories.py +2 -2
- udata/features/identicon/api.py +5 -6
- udata/features/identicon/backends.py +48 -55
- udata/features/identicon/tests/test_backends.py +4 -5
- udata/features/notifications/__init__.py +0 -1
- udata/features/notifications/actions.py +9 -9
- udata/features/notifications/api.py +17 -13
- udata/features/territories/__init__.py +12 -10
- udata/features/territories/api.py +14 -15
- udata/features/territories/models.py +23 -28
- udata/features/transfer/actions.py +8 -11
- udata/features/transfer/api.py +84 -77
- udata/features/transfer/factories.py +2 -1
- udata/features/transfer/models.py +11 -12
- udata/features/transfer/notifications.py +19 -15
- udata/features/transfer/permissions.py +5 -5
- udata/forms/__init__.py +5 -2
- udata/forms/fields.py +164 -172
- udata/forms/validators.py +19 -22
- udata/forms/widgets.py +9 -13
- udata/frontend/__init__.py +31 -26
- udata/frontend/csv.py +68 -58
- udata/frontend/markdown.py +40 -44
- udata/harvest/actions.py +89 -77
- udata/harvest/api.py +294 -238
- udata/harvest/backends/__init__.py +4 -4
- udata/harvest/backends/base.py +128 -111
- udata/harvest/backends/dcat.py +80 -66
- udata/harvest/commands.py +56 -60
- udata/harvest/csv.py +8 -8
- udata/harvest/exceptions.py +6 -3
- udata/harvest/filters.py +24 -23
- udata/harvest/forms.py +27 -28
- udata/harvest/models.py +88 -80
- udata/harvest/notifications.py +15 -10
- udata/harvest/signals.py +13 -13
- udata/harvest/tasks.py +11 -10
- udata/harvest/tests/factories.py +23 -24
- udata/harvest/tests/test_actions.py +136 -166
- udata/harvest/tests/test_api.py +220 -214
- udata/harvest/tests/test_base_backend.py +117 -112
- udata/harvest/tests/test_dcat_backend.py +380 -308
- udata/harvest/tests/test_filters.py +33 -22
- udata/harvest/tests/test_models.py +11 -14
- udata/harvest/tests/test_notifications.py +6 -7
- udata/harvest/tests/test_tasks.py +7 -6
- udata/i18n.py +237 -78
- udata/linkchecker/backends.py +5 -11
- udata/linkchecker/checker.py +23 -22
- udata/linkchecker/commands.py +4 -6
- udata/linkchecker/models.py +6 -6
- udata/linkchecker/tasks.py +18 -20
- udata/mail.py +21 -21
- udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
- udata/migrations/2020-08-24-add-fs-filename.py +9 -8
- udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
- udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
- udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
- udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
- udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
- udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
- udata/migrations/2021-08-17-follow-integrity.py +5 -4
- udata/migrations/2021-08-17-harvest-integrity.py +13 -12
- udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
- udata/migrations/2021-08-17-transfer-integrity.py +5 -4
- udata/migrations/2021-08-17-users-integrity.py +9 -8
- udata/migrations/2021-12-14-reuse-topics.py +7 -6
- udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
- udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
- udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
- udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
- udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
- udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
- udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
- udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
- udata/migrations/__init__.py +123 -105
- udata/models/__init__.py +4 -4
- udata/mongo/__init__.py +13 -11
- udata/mongo/badges_field.py +3 -2
- udata/mongo/datetime_fields.py +13 -12
- udata/mongo/document.py +17 -16
- udata/mongo/engine.py +15 -16
- udata/mongo/errors.py +2 -1
- udata/mongo/extras_fields.py +30 -20
- udata/mongo/queryset.py +12 -12
- udata/mongo/slug_fields.py +38 -28
- udata/mongo/taglist_field.py +1 -2
- udata/mongo/url_field.py +5 -5
- udata/mongo/uuid_fields.py +4 -3
- udata/notifications/__init__.py +1 -1
- udata/notifications/mattermost.py +10 -9
- udata/rdf.py +167 -188
- udata/routing.py +40 -45
- udata/search/__init__.py +18 -19
- udata/search/adapter.py +17 -16
- udata/search/commands.py +44 -51
- udata/search/fields.py +13 -20
- udata/search/query.py +23 -18
- udata/search/result.py +9 -10
- udata/sentry.py +21 -19
- udata/settings.py +262 -198
- udata/sitemap.py +8 -6
- udata/storage/s3.py +20 -13
- udata/tags.py +4 -5
- udata/tasks.py +43 -42
- udata/tests/__init__.py +9 -6
- udata/tests/api/__init__.py +8 -6
- udata/tests/api/test_auth_api.py +395 -321
- udata/tests/api/test_base_api.py +33 -35
- udata/tests/api/test_contact_points.py +7 -9
- udata/tests/api/test_dataservices_api.py +211 -158
- udata/tests/api/test_datasets_api.py +823 -812
- udata/tests/api/test_follow_api.py +13 -15
- udata/tests/api/test_me_api.py +95 -112
- udata/tests/api/test_organizations_api.py +301 -339
- udata/tests/api/test_reports_api.py +35 -25
- udata/tests/api/test_reuses_api.py +134 -139
- udata/tests/api/test_swagger.py +5 -5
- udata/tests/api/test_tags_api.py +18 -25
- udata/tests/api/test_topics_api.py +94 -94
- udata/tests/api/test_transfer_api.py +53 -48
- udata/tests/api/test_user_api.py +128 -141
- udata/tests/apiv2/test_datasets.py +290 -198
- udata/tests/apiv2/test_me_api.py +10 -11
- udata/tests/apiv2/test_organizations.py +56 -74
- udata/tests/apiv2/test_swagger.py +5 -5
- udata/tests/apiv2/test_topics.py +69 -87
- udata/tests/cli/test_cli_base.py +8 -8
- udata/tests/cli/test_db_cli.py +21 -19
- udata/tests/dataservice/test_dataservice_tasks.py +8 -12
- udata/tests/dataset/test_csv_adapter.py +44 -35
- udata/tests/dataset/test_dataset_actions.py +2 -3
- udata/tests/dataset/test_dataset_commands.py +7 -8
- udata/tests/dataset/test_dataset_events.py +36 -29
- udata/tests/dataset/test_dataset_model.py +224 -217
- udata/tests/dataset/test_dataset_rdf.py +142 -131
- udata/tests/dataset/test_dataset_tasks.py +15 -15
- udata/tests/dataset/test_resource_preview.py +10 -13
- udata/tests/features/territories/__init__.py +9 -13
- udata/tests/features/territories/test_territories_api.py +71 -91
- udata/tests/forms/test_basic_fields.py +7 -7
- udata/tests/forms/test_current_user_field.py +39 -66
- udata/tests/forms/test_daterange_field.py +31 -39
- udata/tests/forms/test_dict_field.py +28 -26
- udata/tests/forms/test_extras_fields.py +102 -76
- udata/tests/forms/test_form_field.py +8 -8
- udata/tests/forms/test_image_field.py +33 -26
- udata/tests/forms/test_model_field.py +134 -123
- udata/tests/forms/test_model_list_field.py +7 -7
- udata/tests/forms/test_nested_model_list_field.py +117 -79
- udata/tests/forms/test_publish_as_field.py +36 -65
- udata/tests/forms/test_reference_field.py +34 -53
- udata/tests/forms/test_user_forms.py +23 -21
- udata/tests/forms/test_uuid_field.py +6 -10
- udata/tests/frontend/__init__.py +9 -6
- udata/tests/frontend/test_auth.py +7 -6
- udata/tests/frontend/test_csv.py +81 -96
- udata/tests/frontend/test_hooks.py +43 -43
- udata/tests/frontend/test_markdown.py +211 -191
- udata/tests/helpers.py +32 -37
- udata/tests/models.py +2 -2
- udata/tests/organization/test_csv_adapter.py +21 -16
- udata/tests/organization/test_notifications.py +11 -18
- udata/tests/organization/test_organization_model.py +13 -13
- udata/tests/organization/test_organization_rdf.py +29 -22
- udata/tests/organization/test_organization_tasks.py +16 -17
- udata/tests/plugin.py +79 -73
- udata/tests/reuse/test_reuse_model.py +21 -21
- udata/tests/reuse/test_reuse_task.py +11 -13
- udata/tests/search/__init__.py +11 -12
- udata/tests/search/test_adapter.py +60 -70
- udata/tests/search/test_query.py +16 -16
- udata/tests/search/test_results.py +10 -7
- udata/tests/site/test_site_api.py +11 -16
- udata/tests/site/test_site_metrics.py +20 -30
- udata/tests/site/test_site_model.py +4 -5
- udata/tests/site/test_site_rdf.py +94 -78
- udata/tests/test_activity.py +17 -17
- udata/tests/test_cors.py +62 -0
- udata/tests/test_discussions.py +292 -299
- udata/tests/test_i18n.py +37 -40
- udata/tests/test_linkchecker.py +91 -85
- udata/tests/test_mail.py +13 -17
- udata/tests/test_migrations.py +219 -180
- udata/tests/test_model.py +164 -157
- udata/tests/test_notifications.py +17 -17
- udata/tests/test_owned.py +14 -14
- udata/tests/test_rdf.py +25 -23
- udata/tests/test_routing.py +89 -93
- udata/tests/test_storages.py +137 -128
- udata/tests/test_tags.py +44 -46
- udata/tests/test_topics.py +7 -7
- udata/tests/test_transfer.py +42 -49
- udata/tests/test_uris.py +160 -161
- udata/tests/test_utils.py +79 -71
- udata/tests/user/test_user_rdf.py +5 -9
- udata/tests/workers/test_jobs_commands.py +57 -58
- udata/tests/workers/test_tasks_routing.py +23 -29
- udata/tests/workers/test_workers_api.py +125 -131
- udata/tests/workers/test_workers_helpers.py +6 -6
- udata/tracking.py +4 -6
- udata/uris.py +45 -46
- udata/utils.py +68 -66
- udata/wsgi.py +1 -1
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/METADATA +7 -3
- udata-9.1.2.dev30454.dist-info/RECORD +706 -0
- udata-9.1.2.dev30355.dist-info/RECORD +0 -704
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/LICENSE +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/WHEEL +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/entry_points.txt +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/top_level.txt +0 -0
udata/core/site/models.py
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
from flask import
|
|
1
|
+
from flask import current_app, g
|
|
2
2
|
from werkzeug.local import LocalProxy
|
|
3
3
|
|
|
4
|
-
from udata.models import db, WithMetrics
|
|
5
|
-
from udata.core.organization.models import Organization
|
|
6
4
|
from udata.core.dataset.models import Dataset
|
|
5
|
+
from udata.core.organization.models import Organization
|
|
7
6
|
from udata.core.reuse.models import Reuse
|
|
7
|
+
from udata.models import WithMetrics, db
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
__all__ = ('Site', 'SiteSettings')
|
|
9
|
+
__all__ = ("Site", "SiteSettings")
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
DEFAULT_FEED_SIZE = 20
|
|
@@ -28,126 +27,158 @@ class Site(WithMetrics, db.Document):
|
|
|
28
27
|
settings = db.EmbeddedDocumentField(SiteSettings, default=SiteSettings)
|
|
29
28
|
|
|
30
29
|
__metrics_keys__ = [
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
30
|
+
"max_dataset_followers",
|
|
31
|
+
"max_dataset_reuses",
|
|
32
|
+
"max_reuse_datasets",
|
|
33
|
+
"max_reuse_followers",
|
|
34
|
+
"max_org_followers",
|
|
35
|
+
"max_org_reuses",
|
|
36
|
+
"max_org_datasets",
|
|
37
|
+
"datasets",
|
|
38
|
+
"discussions",
|
|
39
|
+
"followers",
|
|
40
|
+
"organizations",
|
|
41
|
+
"public-service",
|
|
42
|
+
"resources",
|
|
43
|
+
"reuses",
|
|
44
|
+
"users",
|
|
45
|
+
"harvesters",
|
|
47
46
|
]
|
|
48
47
|
|
|
49
48
|
def __str__(self):
|
|
50
|
-
return self.title or
|
|
49
|
+
return self.title or ""
|
|
51
50
|
|
|
52
51
|
def count_users(self):
|
|
53
52
|
from udata.models import User
|
|
54
|
-
|
|
53
|
+
|
|
54
|
+
self.metrics["users"] = User.objects(confirmed_at__ne=None, deleted=None).count()
|
|
55
55
|
self.save()
|
|
56
56
|
|
|
57
57
|
def count_org(self):
|
|
58
58
|
from udata.models import Organization
|
|
59
|
-
|
|
59
|
+
|
|
60
|
+
self.metrics["organizations"] = Organization.objects.visible().count()
|
|
60
61
|
self.save()
|
|
61
62
|
|
|
62
63
|
def count_org_for_badge(self, badge_kind):
|
|
63
64
|
from udata.models import Organization
|
|
65
|
+
|
|
64
66
|
self.metrics[badge_kind] = Organization.objects(badges__kind=badge_kind).count()
|
|
65
67
|
self.save()
|
|
66
68
|
|
|
67
69
|
def count_datasets(self):
|
|
68
70
|
from udata.models import Dataset
|
|
69
|
-
|
|
71
|
+
|
|
72
|
+
self.metrics["datasets"] = Dataset.objects.visible().count()
|
|
70
73
|
self.save()
|
|
71
74
|
|
|
72
75
|
def count_resources(self):
|
|
73
|
-
self.metrics[
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
self.metrics["resources"] = next(
|
|
77
|
+
Dataset.objects.visible().aggregate(
|
|
78
|
+
{"$project": {"resources": 1}},
|
|
79
|
+
{"$unwind": "$resources"},
|
|
80
|
+
{"$group": {"_id": "result", "count": {"$sum": 1}}},
|
|
81
|
+
),
|
|
82
|
+
{},
|
|
83
|
+
).get("count", 0)
|
|
78
84
|
self.save()
|
|
79
85
|
|
|
80
86
|
def count_reuses(self):
|
|
81
|
-
self.metrics[
|
|
87
|
+
self.metrics["reuses"] = Reuse.objects.visible().count()
|
|
82
88
|
self.save()
|
|
83
89
|
|
|
84
90
|
def count_followers(self):
|
|
85
91
|
from udata.models import Follow
|
|
86
|
-
|
|
92
|
+
|
|
93
|
+
self.metrics["followers"] = Follow.objects(until=None).count()
|
|
87
94
|
self.save()
|
|
88
95
|
|
|
89
96
|
def count_discussions(self):
|
|
90
97
|
from udata.models import Discussion
|
|
91
|
-
|
|
98
|
+
|
|
99
|
+
self.metrics["discussions"] = Discussion.objects.count()
|
|
92
100
|
self.save()
|
|
93
101
|
|
|
94
102
|
def count_harvesters(self):
|
|
95
103
|
from udata.harvest.models import HarvestSource
|
|
96
|
-
|
|
104
|
+
|
|
105
|
+
self.metrics["harvesters"] = HarvestSource.objects().count()
|
|
97
106
|
self.save()
|
|
98
107
|
|
|
99
108
|
def count_max_dataset_followers(self):
|
|
100
|
-
dataset = (
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
dataset = (
|
|
110
|
+
Dataset.objects(metrics__followers__gt=0)
|
|
111
|
+
.visible()
|
|
112
|
+
.order_by("-metrics.followers")
|
|
113
|
+
.first()
|
|
114
|
+
)
|
|
115
|
+
self.metrics["max_dataset_followers"] = dataset.metrics["followers"] if dataset else 0
|
|
103
116
|
self.save()
|
|
104
117
|
|
|
105
118
|
def count_max_dataset_reuses(self):
|
|
106
|
-
dataset = (
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
dataset = (
|
|
120
|
+
Dataset.objects(metrics__reuses__gt=0).visible().order_by("-metrics.reuses").first()
|
|
121
|
+
)
|
|
122
|
+
self.metrics["max_dataset_reuses"] = dataset.metrics["reuses"] if dataset else 0
|
|
109
123
|
self.save()
|
|
110
124
|
|
|
111
125
|
def count_max_reuse_datasets(self):
|
|
112
|
-
reuse = (
|
|
113
|
-
|
|
114
|
-
|
|
126
|
+
reuse = (
|
|
127
|
+
Reuse.objects(metrics__datasets__gt=0).visible().order_by("-metrics.datasets").first()
|
|
128
|
+
)
|
|
129
|
+
self.metrics["max_reuse_datasets"] = reuse.metrics["datasets"] if reuse else 0
|
|
115
130
|
self.save()
|
|
116
131
|
|
|
117
132
|
def count_max_reuse_followers(self):
|
|
118
|
-
reuse = (
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
reuse = (
|
|
134
|
+
Reuse.objects(metrics__followers__gt=0).visible().order_by("-metrics.followers").first()
|
|
135
|
+
)
|
|
136
|
+
self.metrics["max_reuse_followers"] = reuse.metrics["followers"] if reuse else 0
|
|
121
137
|
self.save()
|
|
122
138
|
|
|
123
139
|
def count_max_org_followers(self):
|
|
124
|
-
org = (
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
org = (
|
|
141
|
+
Organization.objects(metrics__followers__gt=0)
|
|
142
|
+
.visible()
|
|
143
|
+
.order_by("-metrics.followers")
|
|
144
|
+
.first()
|
|
145
|
+
)
|
|
146
|
+
self.metrics["max_org_followers"] = org.metrics["followers"] if org else 0
|
|
127
147
|
self.save()
|
|
128
148
|
|
|
129
149
|
def count_max_org_reuses(self):
|
|
130
|
-
org = (
|
|
131
|
-
|
|
132
|
-
|
|
150
|
+
org = (
|
|
151
|
+
Organization.objects(metrics__reuses__gt=0)
|
|
152
|
+
.visible()
|
|
153
|
+
.order_by("-metrics.reuses")
|
|
154
|
+
.first()
|
|
155
|
+
)
|
|
156
|
+
self.metrics["max_org_reuses"] = org.metrics["reuses"] if org else 0
|
|
133
157
|
self.save()
|
|
134
158
|
|
|
135
159
|
def count_max_org_datasets(self):
|
|
136
|
-
org = (
|
|
137
|
-
|
|
138
|
-
|
|
160
|
+
org = (
|
|
161
|
+
Organization.objects(metrics__datasets__gt=0)
|
|
162
|
+
.visible()
|
|
163
|
+
.order_by("-metrics.datasets")
|
|
164
|
+
.first()
|
|
165
|
+
)
|
|
166
|
+
self.metrics["max_org_datasets"] = org.metrics["datasets"] if org else 0
|
|
139
167
|
self.save()
|
|
140
168
|
|
|
141
169
|
|
|
142
170
|
def get_current_site():
|
|
143
|
-
if getattr(g,
|
|
144
|
-
site_id = current_app.config[
|
|
145
|
-
site_title = current_app.config.get(
|
|
146
|
-
site_keywords = current_app.config.get(
|
|
147
|
-
g.site, _ = Site.objects.get_or_create(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
171
|
+
if getattr(g, "site", None) is None:
|
|
172
|
+
site_id = current_app.config["SITE_ID"]
|
|
173
|
+
site_title = current_app.config.get("SITE_TITLE")
|
|
174
|
+
site_keywords = current_app.config.get("SITE_KEYWORDS", [])
|
|
175
|
+
g.site, _ = Site.objects.get_or_create(
|
|
176
|
+
id=site_id,
|
|
177
|
+
defaults={
|
|
178
|
+
"title": site_title,
|
|
179
|
+
"keywords": site_keywords,
|
|
180
|
+
},
|
|
181
|
+
)
|
|
151
182
|
if g.site.title != site_title:
|
|
152
183
|
Site.objects(id=site_id).modify(set__title=site_title)
|
|
153
184
|
if g.site.keywords != site_keywords:
|
udata/core/site/rdf.py
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
2
|
This module centralize site helpers for RDF/DCAT serialization and parsing
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
from rdflib
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from flask import current_app, url_for
|
|
6
|
+
from rdflib import BNode, Graph, Literal, URIRef
|
|
7
|
+
from rdflib.namespace import FOAF, RDF
|
|
7
8
|
|
|
8
9
|
from udata.core.dataservices.rdf import dataservice_to_rdf
|
|
9
10
|
from udata.core.dataset.rdf import dataset_to_rdf
|
|
10
11
|
from udata.core.organization.rdf import organization_to_rdf
|
|
11
12
|
from udata.core.user.rdf import user_to_rdf
|
|
12
13
|
from udata.rdf import DCAT, DCT, namespace_manager, paginate_catalog
|
|
13
|
-
from udata.utils import Paginable
|
|
14
14
|
from udata.uris import endpoint_for
|
|
15
|
+
from udata.utils import Paginable
|
|
15
16
|
|
|
16
17
|
|
|
17
|
-
def build_catalog(site, datasets, dataservices
|
|
18
|
-
|
|
19
|
-
site_url = endpoint_for(
|
|
20
|
-
catalog_url = url_for(
|
|
18
|
+
def build_catalog(site, datasets, dataservices=[], format=None):
|
|
19
|
+
"""Build the DCAT catalog for this site"""
|
|
20
|
+
site_url = endpoint_for("site.home_redirect", "api.site", _external=True)
|
|
21
|
+
catalog_url = url_for("api.site_rdf_catalog", _external=True)
|
|
21
22
|
graph = Graph(namespace_manager=namespace_manager)
|
|
22
23
|
catalog = graph.resource(URIRef(catalog_url))
|
|
23
24
|
|
|
24
25
|
catalog.set(RDF.type, DCAT.Catalog)
|
|
25
26
|
catalog.set(DCT.title, Literal(site.title))
|
|
26
27
|
catalog.set(DCT.description, Literal(f"{site.title}"))
|
|
27
|
-
catalog.set(DCT.language,
|
|
28
|
-
Literal(current_app.config['DEFAULT_LANGUAGE']))
|
|
28
|
+
catalog.set(DCT.language, Literal(current_app.config["DEFAULT_LANGUAGE"]))
|
|
29
29
|
catalog.set(FOAF.homepage, URIRef(site_url))
|
|
30
30
|
|
|
31
31
|
publisher = graph.resource(BNode())
|
|
32
32
|
publisher.set(RDF.type, FOAF.Organization)
|
|
33
|
-
publisher.set(FOAF.name, Literal(current_app.config[
|
|
33
|
+
publisher.set(FOAF.name, Literal(current_app.config["SITE_AUTHOR"]))
|
|
34
34
|
catalog.set(DCT.publisher, publisher)
|
|
35
35
|
|
|
36
36
|
for dataset in datasets:
|
|
@@ -46,6 +46,6 @@ def build_catalog(site, datasets, dataservices = [], format=None):
|
|
|
46
46
|
catalog.add(DCAT.DataService, rdf_dataservice)
|
|
47
47
|
|
|
48
48
|
if isinstance(datasets, Paginable):
|
|
49
|
-
paginate_catalog(catalog, graph, datasets, format,
|
|
49
|
+
paginate_catalog(catalog, graph, datasets, format, "api.site_rdf_catalog_format")
|
|
50
50
|
|
|
51
51
|
return catalog
|
udata/core/spam/api.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from mongoengine import Q
|
|
2
2
|
|
|
3
|
-
from udata.api import
|
|
3
|
+
from udata.api import API, api
|
|
4
4
|
from udata.auth import admin_permission
|
|
5
5
|
from udata.core.discussions.models import Discussion
|
|
6
|
-
from udata.core.spam.fields import potential_spam_fields
|
|
7
6
|
from udata.core.spam.constants import POTENTIAL_SPAM
|
|
7
|
+
from udata.core.spam.fields import potential_spam_fields
|
|
8
8
|
from udata.utils import id_or_404
|
|
9
9
|
|
|
10
10
|
|
|
@@ -12,6 +12,7 @@ class SpamAPIMixin(API):
|
|
|
12
12
|
"""
|
|
13
13
|
Base Spam Model API.
|
|
14
14
|
"""
|
|
15
|
+
|
|
15
16
|
model = None
|
|
16
17
|
|
|
17
18
|
def get_model(self, id):
|
|
@@ -37,21 +38,27 @@ class SpamAPIMixin(API):
|
|
|
37
38
|
return {}, 200
|
|
38
39
|
|
|
39
40
|
|
|
40
|
-
ns = api.namespace(
|
|
41
|
+
ns = api.namespace("spam", "Spam related operations")
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
@ns.route(
|
|
44
|
+
@ns.route("/", endpoint="spam")
|
|
44
45
|
class SpamAPI(API):
|
|
45
46
|
"""
|
|
46
47
|
Base class for a discussion thread.
|
|
47
48
|
"""
|
|
48
|
-
|
|
49
|
+
|
|
50
|
+
@api.doc("get_potential_spams")
|
|
49
51
|
@api.secure(admin_permission)
|
|
50
52
|
@api.marshal_with(potential_spam_fields)
|
|
51
53
|
def get(self):
|
|
52
54
|
"""Get all potential spam objects"""
|
|
53
|
-
discussions = Discussion.objects(
|
|
55
|
+
discussions = Discussion.objects(
|
|
56
|
+
Q(spam__status=POTENTIAL_SPAM) | Q(discussion__spam__status=POTENTIAL_SPAM)
|
|
57
|
+
)
|
|
54
58
|
|
|
55
|
-
return [
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
return [
|
|
60
|
+
{
|
|
61
|
+
"message": discussion.spam_report_message([discussion]),
|
|
62
|
+
}
|
|
63
|
+
for discussion in discussions
|
|
64
|
+
]
|
udata/core/spam/constants.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
NOT_CHECKED =
|
|
2
|
-
POTENTIAL_SPAM =
|
|
3
|
-
NO_SPAM =
|
|
1
|
+
NOT_CHECKED = "not_checked"
|
|
2
|
+
POTENTIAL_SPAM = "potential_spam"
|
|
3
|
+
NO_SPAM = "no_spam"
|
|
4
4
|
|
|
5
|
-
SPAM_STATUS_CHOICES = [NOT_CHECKED, POTENTIAL_SPAM, NO_SPAM]
|
|
5
|
+
SPAM_STATUS_CHOICES = [NOT_CHECKED, POTENTIAL_SPAM, NO_SPAM]
|
udata/core/spam/fields.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
from udata.api import api, fields
|
|
2
|
-
from .constants import SPAM_STATUS_CHOICES
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
'status': fields.String(description='Status', enum=SPAM_STATUS_CHOICES, readonly=True),
|
|
6
|
-
})
|
|
3
|
+
from .constants import SPAM_STATUS_CHOICES
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
spam_fields = api.model(
|
|
6
|
+
"Spam",
|
|
7
|
+
{
|
|
8
|
+
"status": fields.String(description="Status", enum=SPAM_STATUS_CHOICES, readonly=True),
|
|
9
|
+
},
|
|
10
|
+
)
|
|
11
11
|
|
|
12
|
+
potential_spam_fields = api.model(
|
|
13
|
+
"PotentialSpam",
|
|
14
|
+
{
|
|
15
|
+
"message": fields.String(readonly=True),
|
|
16
|
+
},
|
|
17
|
+
)
|
udata/core/spam/models.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
from flask import current_app
|
|
2
2
|
from langdetect import detect
|
|
3
|
+
from mongoengine import signals
|
|
3
4
|
|
|
4
5
|
from udata.mongo import db
|
|
5
|
-
from .signals import on_new_potential_spam
|
|
6
|
-
from mongoengine import signals
|
|
7
6
|
|
|
8
|
-
from .constants import NO_SPAM, POTENTIAL_SPAM, SPAM_STATUS_CHOICES
|
|
7
|
+
from .constants import NO_SPAM, NOT_CHECKED, POTENTIAL_SPAM, SPAM_STATUS_CHOICES
|
|
8
|
+
from .signals import on_new_potential_spam
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class SpamInfo(db.EmbeddedDocument):
|
|
@@ -28,16 +28,15 @@ class SpamMixin(object):
|
|
|
28
28
|
|
|
29
29
|
@staticmethod
|
|
30
30
|
def spam_words():
|
|
31
|
-
return current_app.config.get(
|
|
31
|
+
return current_app.config.get("SPAM_WORDS", [])
|
|
32
32
|
|
|
33
33
|
@staticmethod
|
|
34
34
|
def allowed_langs():
|
|
35
|
-
return current_app.config.get(
|
|
36
|
-
|
|
35
|
+
return current_app.config.get("SPAM_ALLOWED_LANGS", [])
|
|
36
|
+
|
|
37
37
|
@staticmethod
|
|
38
38
|
def minimum_string_length_for_lang_check():
|
|
39
|
-
return current_app.config.get(
|
|
40
|
-
|
|
39
|
+
return current_app.config.get("SPAM_MINIMUM_STRING_LENGTH_FOR_LANG_CHECK", 30)
|
|
41
40
|
|
|
42
41
|
def clean(self):
|
|
43
42
|
super().clean()
|
|
@@ -91,15 +90,22 @@ class SpamMixin(object):
|
|
|
91
90
|
for word in SpamMixin.spam_words():
|
|
92
91
|
if word in text.lower():
|
|
93
92
|
self.spam.status = POTENTIAL_SPAM
|
|
94
|
-
self._report(
|
|
93
|
+
self._report(
|
|
94
|
+
text=text, breadcrumb=breadcrumb, reason=f'contains spam words "{word}"'
|
|
95
|
+
)
|
|
95
96
|
return
|
|
96
97
|
|
|
97
98
|
# Language detection is not working well with texts of a few words.
|
|
98
|
-
if
|
|
99
|
+
if (
|
|
100
|
+
SpamMixin.allowed_langs()
|
|
101
|
+
and len(text) > SpamMixin.minimum_string_length_for_lang_check()
|
|
102
|
+
):
|
|
99
103
|
lang = detect(text.lower())
|
|
100
104
|
if lang not in SpamMixin.allowed_langs():
|
|
101
105
|
self.spam.status = POTENTIAL_SPAM
|
|
102
|
-
self._report(
|
|
106
|
+
self._report(
|
|
107
|
+
text=text, breadcrumb=breadcrumb, reason=f'not allowed language "{lang}"'
|
|
108
|
+
)
|
|
103
109
|
return
|
|
104
110
|
|
|
105
111
|
for embed in self.embeds_to_check_for_spam():
|
|
@@ -130,19 +136,20 @@ class SpamMixin(object):
|
|
|
130
136
|
|
|
131
137
|
for name, args in callbacks.items():
|
|
132
138
|
callback = getattr(base_model, name)
|
|
133
|
-
callback(*args[
|
|
139
|
+
callback(*args["args"], **args["kwargs"])
|
|
134
140
|
|
|
135
141
|
def is_spam(self):
|
|
136
142
|
return self.spam and self.spam.status == POTENTIAL_SPAM
|
|
137
143
|
|
|
138
144
|
def texts_to_check_for_spam(self):
|
|
139
145
|
raise NotImplementedError(
|
|
140
|
-
"Please implement the `texts_to_check_for_spam` method. Should return a list of strings to check."
|
|
146
|
+
"Please implement the `texts_to_check_for_spam` method. Should return a list of strings to check."
|
|
147
|
+
)
|
|
141
148
|
|
|
142
149
|
def embeds_to_check_for_spam(self):
|
|
143
150
|
return []
|
|
144
151
|
|
|
145
|
-
def spam_is_whitelisted(self) -> bool
|
|
152
|
+
def spam_is_whitelisted(self) -> bool:
|
|
146
153
|
return False
|
|
147
154
|
|
|
148
155
|
def spam_report_message(self):
|
|
@@ -177,7 +184,7 @@ def spam_protected(get_model_to_check=None):
|
|
|
177
184
|
It will save the class method called with its arguments inside the `SpamInfo` object to be
|
|
178
185
|
called later if needed (in case of false positive).
|
|
179
186
|
The decorator accept an argument, a function to get the model to check when we are doing an operation
|
|
180
|
-
on an embed document. The class method should always take a `self` as a first argument which is the base
|
|
187
|
+
on an embed document. The class method should always take a `self` as a first argument which is the base
|
|
181
188
|
model to allow saving the callbacks back into Mongo (we cannot .save() an embed document).
|
|
182
189
|
"""
|
|
183
190
|
|
|
@@ -191,13 +198,13 @@ def spam_protected(get_model_to_check=None):
|
|
|
191
198
|
|
|
192
199
|
if not isinstance(model_to_check, SpamMixin):
|
|
193
200
|
raise ValueError(
|
|
194
|
-
"@spam_protected should be called within a SpamMixin. "
|
|
201
|
+
"@spam_protected should be called within a SpamMixin. "
|
|
202
|
+
+ type(model_to_check).__name__
|
|
203
|
+
+ " given."
|
|
204
|
+
)
|
|
195
205
|
|
|
196
206
|
if model_to_check.is_spam():
|
|
197
|
-
model_to_check.spam.callbacks[f.__name__] = {
|
|
198
|
-
'args': args[1:],
|
|
199
|
-
'kwargs': kwargs
|
|
200
|
-
}
|
|
207
|
+
model_to_check.spam.callbacks[f.__name__] = {"args": args[1:], "kwargs": kwargs}
|
|
201
208
|
base_model.save_without_spam_detection()
|
|
202
209
|
else:
|
|
203
210
|
f(*args, **kwargs)
|
udata/core/spam/signals.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
|
|
2
3
|
import pytest
|
|
3
|
-
|
|
4
|
+
|
|
4
5
|
from udata.mongo import db
|
|
6
|
+
from udata.tests import TestCase
|
|
5
7
|
|
|
6
|
-
from ..models import SpamMixin
|
|
7
8
|
from ..constants import POTENTIAL_SPAM
|
|
8
|
-
|
|
9
|
+
from ..models import SpamMixin
|
|
9
10
|
|
|
10
11
|
log = logging.getLogger(__name__)
|
|
11
12
|
|
|
13
|
+
|
|
12
14
|
class TestModel(SpamMixin, db.Document):
|
|
13
15
|
text = db.StringField(required=True)
|
|
14
16
|
_created = True
|
|
@@ -18,9 +20,8 @@ class TestModel(SpamMixin, db.Document):
|
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class SpamTest(TestCase):
|
|
21
|
-
@pytest.mark.options(SPAM_WORDS=[
|
|
23
|
+
@pytest.mark.options(SPAM_WORDS=["spam"], SPAM_ALLOWED_LANGS=["fr"])
|
|
22
24
|
def test_uppercase_lang_detect(self):
|
|
23
25
|
model = TestModel(text="DONNEES DE RECENSEMENT - MARCHES PUBLICS")
|
|
24
26
|
model.detect_spam()
|
|
25
27
|
self.assertNotEqual(model.spam.status, POTENTIAL_SPAM)
|
|
26
|
-
|