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
|
@@ -5,288 +5,282 @@ from flask import url_for
|
|
|
5
5
|
from udata.core.jobs.models import PeriodicTask
|
|
6
6
|
from udata.core.user.factories import AdminFactory
|
|
7
7
|
from udata.tasks import celery, job
|
|
8
|
-
from udata.utils import faker
|
|
9
|
-
|
|
10
8
|
from udata.tests.api import APITestCase
|
|
9
|
+
from udata.utils import faker
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class JobsAPITest(APITestCase):
|
|
14
13
|
def test_schedulable_jobs_list(self):
|
|
15
|
-
@celery.task(name=
|
|
14
|
+
@celery.task(name="a-schedulable-job", schedulable=True)
|
|
16
15
|
def test_job():
|
|
17
16
|
pass
|
|
18
17
|
|
|
19
|
-
response = self.get(url_for(
|
|
18
|
+
response = self.get(url_for("api.schedulable_jobs"))
|
|
20
19
|
self.assert200(response)
|
|
21
|
-
self.assertIn(
|
|
20
|
+
self.assertIn("a-schedulable-job", response.json)
|
|
22
21
|
|
|
23
22
|
def test_schedulable_jobs_list_with_decorator(self):
|
|
24
|
-
@job(
|
|
23
|
+
@job("a-job")
|
|
25
24
|
def test_job():
|
|
26
25
|
pass
|
|
27
26
|
|
|
28
|
-
response = self.get(url_for(
|
|
27
|
+
response = self.get(url_for("api.schedulable_jobs"))
|
|
29
28
|
self.assert200(response)
|
|
30
|
-
self.assertIn(
|
|
29
|
+
self.assertIn("a-job", response.json)
|
|
31
30
|
|
|
32
31
|
def test_scheduled_jobs_list(self):
|
|
33
|
-
@job(
|
|
32
|
+
@job("a-job")
|
|
34
33
|
def test_job():
|
|
35
34
|
pass
|
|
36
35
|
|
|
37
36
|
for i in range(6):
|
|
38
|
-
params = {
|
|
39
|
-
'name': faker.name(),
|
|
40
|
-
'description': faker.sentence(),
|
|
41
|
-
'task': 'a-job'
|
|
42
|
-
}
|
|
37
|
+
params = {"name": faker.name(), "description": faker.sentence(), "task": "a-job"}
|
|
43
38
|
if i % 2:
|
|
44
|
-
params[
|
|
39
|
+
params["crontab"] = PeriodicTask.Crontab(minute=str(i))
|
|
45
40
|
else:
|
|
46
|
-
params[
|
|
47
|
-
period='minutes')
|
|
41
|
+
params["interval"] = PeriodicTask.Interval(every=i, period="minutes")
|
|
48
42
|
PeriodicTask.objects.create(**params)
|
|
49
43
|
|
|
50
|
-
response = self.get(url_for(
|
|
44
|
+
response = self.get(url_for("api.jobs"))
|
|
51
45
|
self.assert200(response)
|
|
52
46
|
|
|
53
47
|
def test_create_job_need_admin(self):
|
|
54
|
-
@job(
|
|
48
|
+
@job("a-job")
|
|
55
49
|
def test_job():
|
|
56
50
|
pass
|
|
57
51
|
|
|
58
52
|
data = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
53
|
+
"name": "A crontab job",
|
|
54
|
+
"description": "A simple crontab job doing nothing",
|
|
55
|
+
"task": "a-job",
|
|
62
56
|
}
|
|
63
57
|
|
|
64
58
|
self.login()
|
|
65
|
-
response = self.post(url_for(
|
|
59
|
+
response = self.post(url_for("api.jobs"), data)
|
|
66
60
|
self.assert403(response)
|
|
67
61
|
|
|
68
62
|
def test_create_crontab_job(self):
|
|
69
|
-
@job(
|
|
63
|
+
@job("a-job")
|
|
70
64
|
def test_job():
|
|
71
65
|
pass
|
|
72
66
|
|
|
73
67
|
data = {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
'minute': '0',
|
|
79
|
-
'hour': '0'
|
|
80
|
-
}
|
|
68
|
+
"name": "A crontab job",
|
|
69
|
+
"description": "A simple crontab job doing nothing",
|
|
70
|
+
"task": "a-job",
|
|
71
|
+
"crontab": {"minute": "0", "hour": "0"},
|
|
81
72
|
}
|
|
82
73
|
|
|
83
74
|
self.login(AdminFactory())
|
|
84
|
-
response = self.post(url_for(
|
|
75
|
+
response = self.post(url_for("api.jobs"), data)
|
|
85
76
|
self.assert201(response)
|
|
86
77
|
|
|
87
|
-
self.assertEqual(response.json[
|
|
88
|
-
self.assertEqual(response.json[
|
|
89
|
-
self.assertEqual(response.json[
|
|
90
|
-
self.assertEqual(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
self.assertEqual(response.json["name"], data["name"])
|
|
79
|
+
self.assertEqual(response.json["description"], data["description"])
|
|
80
|
+
self.assertEqual(response.json["task"], data["task"])
|
|
81
|
+
self.assertEqual(
|
|
82
|
+
response.json["crontab"],
|
|
83
|
+
{
|
|
84
|
+
"minute": "0",
|
|
85
|
+
"hour": "0",
|
|
86
|
+
"day_of_week": "*",
|
|
87
|
+
"day_of_month": "*",
|
|
88
|
+
"month_of_year": "*",
|
|
89
|
+
},
|
|
90
|
+
)
|
|
97
91
|
|
|
98
92
|
def test_create_interval_job(self):
|
|
99
|
-
@job(
|
|
93
|
+
@job("a-job")
|
|
100
94
|
def test_job():
|
|
101
95
|
pass
|
|
102
96
|
|
|
103
97
|
data = {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
'every': 5,
|
|
109
|
-
'period': 'minutes'
|
|
110
|
-
}
|
|
98
|
+
"name": "An interval job",
|
|
99
|
+
"description": "A simple interval job doing nothing",
|
|
100
|
+
"task": "a-job",
|
|
101
|
+
"interval": {"every": 5, "period": "minutes"},
|
|
111
102
|
}
|
|
112
103
|
|
|
113
104
|
self.login(AdminFactory())
|
|
114
|
-
response = self.post(url_for(
|
|
105
|
+
response = self.post(url_for("api.jobs"), data)
|
|
115
106
|
self.assert201(response)
|
|
116
107
|
|
|
117
|
-
self.assertEqual(response.json[
|
|
118
|
-
self.assertEqual(response.json[
|
|
119
|
-
self.assertEqual(response.json[
|
|
120
|
-
self.assertEqual(response.json[
|
|
108
|
+
self.assertEqual(response.json["name"], data["name"])
|
|
109
|
+
self.assertEqual(response.json["description"], data["description"])
|
|
110
|
+
self.assertEqual(response.json["task"], data["task"])
|
|
111
|
+
self.assertEqual(response.json["interval"], data["interval"])
|
|
121
112
|
|
|
122
113
|
def test_fail_on_create_with_both_crontab_and_interval(self):
|
|
123
|
-
@job(
|
|
114
|
+
@job("a-job")
|
|
124
115
|
def test_job():
|
|
125
116
|
pass
|
|
126
117
|
|
|
127
118
|
data = {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
'hour': '0'
|
|
134
|
-
},
|
|
135
|
-
'interval': {
|
|
136
|
-
'every': 5,
|
|
137
|
-
'period': 'minutes'
|
|
138
|
-
}
|
|
119
|
+
"name": "A mixed job",
|
|
120
|
+
"description": "A simple crontab job doing nothing",
|
|
121
|
+
"task": "a-job",
|
|
122
|
+
"crontab": {"minute": "0", "hour": "0"},
|
|
123
|
+
"interval": {"every": 5, "period": "minutes"},
|
|
139
124
|
}
|
|
140
125
|
|
|
141
126
|
self.login(AdminFactory())
|
|
142
|
-
response = self.post(url_for(
|
|
127
|
+
response = self.post(url_for("api.jobs"), data)
|
|
143
128
|
self.assertStatus(response, 400)
|
|
144
129
|
|
|
145
130
|
def test_create_manual_job(self):
|
|
146
131
|
pass
|
|
147
132
|
|
|
148
133
|
def test_get_job(self):
|
|
149
|
-
@job(
|
|
134
|
+
@job("a-job")
|
|
150
135
|
def test_job():
|
|
151
136
|
pass
|
|
152
137
|
|
|
153
138
|
task = PeriodicTask.objects.create(
|
|
154
139
|
name=faker.name(),
|
|
155
140
|
description=faker.sentence(),
|
|
156
|
-
task=
|
|
157
|
-
crontab=PeriodicTask.Crontab(minute=
|
|
141
|
+
task="a-job",
|
|
142
|
+
crontab=PeriodicTask.Crontab(minute="5"),
|
|
158
143
|
)
|
|
159
144
|
|
|
160
|
-
response = self.get(url_for(
|
|
145
|
+
response = self.get(url_for("api.job", id=task.id))
|
|
161
146
|
self.assert200(response)
|
|
162
|
-
self.assertEqual(response.json[
|
|
163
|
-
self.assertEqual(response.json[
|
|
164
|
-
self.assertEqual(response.json[
|
|
165
|
-
self.assertEqual(response.json[
|
|
147
|
+
self.assertEqual(response.json["id"], str(task.id))
|
|
148
|
+
self.assertEqual(response.json["name"], task.name)
|
|
149
|
+
self.assertEqual(response.json["description"], task.description)
|
|
150
|
+
self.assertEqual(response.json["task"], task.task)
|
|
166
151
|
|
|
167
152
|
def test_update_job_need_admin(self):
|
|
168
|
-
@job(
|
|
153
|
+
@job("a-job")
|
|
169
154
|
def test_job():
|
|
170
155
|
pass
|
|
171
156
|
|
|
172
157
|
task = PeriodicTask.objects.create(
|
|
173
158
|
name=faker.name(),
|
|
174
159
|
description=faker.sentence(),
|
|
175
|
-
task=
|
|
176
|
-
crontab=PeriodicTask.Crontab(minute=
|
|
160
|
+
task="a-job",
|
|
161
|
+
crontab=PeriodicTask.Crontab(minute="5"),
|
|
177
162
|
)
|
|
178
163
|
|
|
179
164
|
self.login()
|
|
180
|
-
response = self.put(
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
165
|
+
response = self.put(
|
|
166
|
+
url_for("api.job", id=task.id),
|
|
167
|
+
{
|
|
168
|
+
"name": task.name,
|
|
169
|
+
"description": "New description",
|
|
170
|
+
"task": task.task,
|
|
171
|
+
"crontab": task.crontab.to_json(),
|
|
172
|
+
},
|
|
173
|
+
)
|
|
186
174
|
self.assert403(response)
|
|
187
175
|
|
|
188
176
|
def test_update_job(self):
|
|
189
|
-
@job(
|
|
177
|
+
@job("a-job")
|
|
190
178
|
def test_job():
|
|
191
179
|
pass
|
|
192
180
|
|
|
193
181
|
task = PeriodicTask.objects.create(
|
|
194
182
|
name=faker.name(),
|
|
195
183
|
description=faker.sentence(),
|
|
196
|
-
task=
|
|
197
|
-
crontab=PeriodicTask.Crontab(minute=
|
|
184
|
+
task="a-job",
|
|
185
|
+
crontab=PeriodicTask.Crontab(minute="5"),
|
|
198
186
|
)
|
|
199
187
|
|
|
200
188
|
self.login(AdminFactory())
|
|
201
|
-
response = self.put(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
189
|
+
response = self.put(
|
|
190
|
+
url_for("api.job", id=task.id),
|
|
191
|
+
{
|
|
192
|
+
"name": task.name,
|
|
193
|
+
"description": "New description",
|
|
194
|
+
"task": task.task,
|
|
195
|
+
"crontab": task.crontab.to_json(),
|
|
196
|
+
},
|
|
197
|
+
)
|
|
207
198
|
self.assert200(response)
|
|
208
199
|
|
|
209
|
-
self.assertEqual(response.json[
|
|
210
|
-
self.assertEqual(response.json[
|
|
211
|
-
self.assertEqual(response.json[
|
|
212
|
-
self.assertEqual(response.json[
|
|
213
|
-
self.assertIsNotNone(response.json[
|
|
214
|
-
self.assertIsNone(response.json[
|
|
200
|
+
self.assertEqual(response.json["id"], str(task.id))
|
|
201
|
+
self.assertEqual(response.json["name"], task.name)
|
|
202
|
+
self.assertEqual(response.json["task"], task.task)
|
|
203
|
+
self.assertEqual(response.json["description"], "New description")
|
|
204
|
+
self.assertIsNotNone(response.json["crontab"])
|
|
205
|
+
self.assertIsNone(response.json["interval"])
|
|
215
206
|
|
|
216
207
|
def test_update_job_change_type(self):
|
|
217
|
-
@job(
|
|
208
|
+
@job("a-job")
|
|
218
209
|
def test_job():
|
|
219
210
|
pass
|
|
220
211
|
|
|
221
212
|
task = PeriodicTask.objects.create(
|
|
222
213
|
name=faker.name(),
|
|
223
214
|
description=faker.sentence(),
|
|
224
|
-
task=
|
|
225
|
-
crontab=PeriodicTask.Crontab(minute=
|
|
215
|
+
task="a-job",
|
|
216
|
+
crontab=PeriodicTask.Crontab(minute="5"),
|
|
226
217
|
)
|
|
227
218
|
|
|
228
219
|
self.login(AdminFactory())
|
|
229
|
-
response = self.put(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
220
|
+
response = self.put(
|
|
221
|
+
url_for("api.job", id=task.id),
|
|
222
|
+
{
|
|
223
|
+
"name": task.name,
|
|
224
|
+
"description": task.description,
|
|
225
|
+
"task": task.task,
|
|
226
|
+
"interval": {
|
|
227
|
+
"every": 5,
|
|
228
|
+
"period": "minutes",
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
)
|
|
238
232
|
self.assert200(response)
|
|
239
233
|
|
|
240
|
-
self.assertEqual(response.json[
|
|
241
|
-
self.assertEqual(response.json[
|
|
242
|
-
self.assertEqual(response.json[
|
|
243
|
-
self.assertEqual(response.json[
|
|
244
|
-
self.assertEqual(response.json[
|
|
245
|
-
self.assertEqual(response.json[
|
|
246
|
-
self.assertIsNone(response.json[
|
|
234
|
+
self.assertEqual(response.json["id"], str(task.id))
|
|
235
|
+
self.assertEqual(response.json["name"], task.name)
|
|
236
|
+
self.assertEqual(response.json["task"], task.task)
|
|
237
|
+
self.assertEqual(response.json["description"], task.description)
|
|
238
|
+
self.assertEqual(response.json["interval"]["every"], 5)
|
|
239
|
+
self.assertEqual(response.json["interval"]["period"], "minutes")
|
|
240
|
+
self.assertIsNone(response.json["crontab"])
|
|
247
241
|
|
|
248
242
|
def test_delete_job_need_admin(self):
|
|
249
|
-
@job(
|
|
243
|
+
@job("a-job")
|
|
250
244
|
def test_job():
|
|
251
245
|
pass
|
|
252
246
|
|
|
253
247
|
task = PeriodicTask.objects.create(
|
|
254
248
|
name=faker.name(),
|
|
255
249
|
description=faker.sentence(),
|
|
256
|
-
task=
|
|
257
|
-
crontab=PeriodicTask.Crontab(minute=
|
|
250
|
+
task="a-job",
|
|
251
|
+
crontab=PeriodicTask.Crontab(minute="5"),
|
|
258
252
|
)
|
|
259
253
|
|
|
260
254
|
self.login()
|
|
261
|
-
response = self.delete(url_for(
|
|
255
|
+
response = self.delete(url_for("api.job", id=task.id))
|
|
262
256
|
self.assert403(response)
|
|
263
257
|
|
|
264
258
|
def test_delete_job(self):
|
|
265
|
-
@job(
|
|
259
|
+
@job("a-job")
|
|
266
260
|
def test_job():
|
|
267
261
|
pass
|
|
268
262
|
|
|
269
263
|
task = PeriodicTask.objects.create(
|
|
270
264
|
name=faker.name(),
|
|
271
265
|
description=faker.sentence(),
|
|
272
|
-
task=
|
|
273
|
-
crontab=PeriodicTask.Crontab(minute=
|
|
266
|
+
task="a-job",
|
|
267
|
+
crontab=PeriodicTask.Crontab(minute="5"),
|
|
274
268
|
)
|
|
275
269
|
|
|
276
270
|
self.login(AdminFactory())
|
|
277
|
-
response = self.delete(url_for(
|
|
271
|
+
response = self.delete(url_for("api.job", id=task.id))
|
|
278
272
|
self.assert204(response)
|
|
279
273
|
|
|
280
274
|
self.assertIsNone(PeriodicTask.objects(id=task.id).first())
|
|
281
275
|
|
|
282
|
-
@skip(
|
|
276
|
+
@skip("Need to be mocked and more details")
|
|
283
277
|
def test_get_task(self):
|
|
284
278
|
@celery.task
|
|
285
279
|
def test_task():
|
|
286
|
-
print(
|
|
280
|
+
print("hello")
|
|
287
281
|
|
|
288
282
|
result = test_task.delay() # Always eager so no async
|
|
289
283
|
|
|
290
|
-
response = self.get(url_for(
|
|
284
|
+
response = self.get(url_for("api.task", id=result.id))
|
|
291
285
|
self.assert200(response)
|
|
292
|
-
self.assertEqual(response.json[
|
|
286
|
+
self.assertEqual(response.json["id"], result.id)
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
from udata.tests import TestCase
|
|
2
1
|
from udata.settings import Defaults
|
|
3
2
|
from udata.tasks import default_scheduler_config
|
|
3
|
+
from udata.tests import TestCase
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class DefaultSchedulerConfigTest(TestCase):
|
|
7
7
|
def test_parse_default_value(self):
|
|
8
8
|
db, url = default_scheduler_config(Defaults.MONGODB_HOST)
|
|
9
|
-
self.assertEqual(db,
|
|
10
|
-
self.assertEqual(url,
|
|
9
|
+
self.assertEqual(db, "udata")
|
|
10
|
+
self.assertEqual(url, "mongodb://localhost:27017")
|
|
11
11
|
|
|
12
12
|
def test_parse_url_with_auth(self):
|
|
13
|
-
full_url =
|
|
13
|
+
full_url = "mongodb://userid:password@somewhere.com:1234/mydb"
|
|
14
14
|
db, url = default_scheduler_config(full_url)
|
|
15
|
-
self.assertEqual(db,
|
|
16
|
-
self.assertEqual(url,
|
|
15
|
+
self.assertEqual(db, "mydb")
|
|
16
|
+
self.assertEqual(url, "mongodb://userid:password@somewhere.com:1234")
|
udata/tracking.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
def send_signal(signal, request, user, **kwargs):
|
|
2
|
-
|
|
2
|
+
"""Generic method to send signals to Piwik
|
|
3
3
|
|
|
4
4
|
given that we always have to compute IP and UID for instance.
|
|
5
|
-
|
|
6
|
-
params = {
|
|
7
|
-
'user_ip': request.remote_addr
|
|
8
|
-
}
|
|
5
|
+
"""
|
|
6
|
+
params = {"user_ip": request.remote_addr}
|
|
9
7
|
params.update(kwargs)
|
|
10
8
|
if user.is_authenticated:
|
|
11
|
-
params[
|
|
9
|
+
params["uid"] = user.id
|
|
12
10
|
signal.send(request.url, **params)
|
udata/uris.py
CHANGED
|
@@ -1,48 +1,49 @@
|
|
|
1
1
|
import re
|
|
2
2
|
|
|
3
|
-
from werkzeug.routing import BuildError
|
|
4
3
|
from flask import current_app, url_for
|
|
5
|
-
from netaddr import
|
|
6
|
-
from
|
|
4
|
+
from netaddr import AddrFormatError, IPAddress
|
|
5
|
+
from werkzeug.routing import BuildError
|
|
7
6
|
|
|
7
|
+
from udata.i18n import _
|
|
8
8
|
from udata.settings import Defaults
|
|
9
9
|
|
|
10
10
|
URL_REGEX = re.compile(
|
|
11
|
-
r
|
|
11
|
+
r"^"
|
|
12
12
|
# scheme
|
|
13
|
-
r
|
|
13
|
+
r"^(?:(?P<scheme>[a-z0-9\.\-]*):)?//"
|
|
14
14
|
# user:pass authentication
|
|
15
|
-
r
|
|
16
|
-
r
|
|
15
|
+
r"(?P<credentials>\S+(?::\S*)?@)?"
|
|
16
|
+
r"(?:"
|
|
17
17
|
# localhost
|
|
18
|
-
r
|
|
19
|
-
r
|
|
18
|
+
r"(?P<localhost>localhost(?:\.localdomain)?)"
|
|
19
|
+
r"|"
|
|
20
20
|
# IPv4 addresses
|
|
21
|
-
r
|
|
22
|
-
r
|
|
21
|
+
r"(?P<ipv4>(?:\d{,3}\.){3}(?:\d{,3}))"
|
|
22
|
+
r"|"
|
|
23
23
|
# IPv6 address
|
|
24
|
-
r
|
|
25
|
-
r
|
|
24
|
+
r"(?:\[(?P<ipv6>[0-9a-f:]+)\])"
|
|
25
|
+
r"|"
|
|
26
26
|
# host name
|
|
27
|
-
r
|
|
27
|
+
r"(?:(?:[a-z\u00a1-\uffff0-9_]-?)*[a-z\u00a1-\uffff0-9]+)"
|
|
28
28
|
# domain name
|
|
29
|
-
r
|
|
29
|
+
r"(?:\.(?:[a-z\u00a1-\uffff0-9_]-?)*[a-z\u00a1-\uffff0-9]+)*"
|
|
30
30
|
# TLD identifier
|
|
31
|
-
r
|
|
32
|
-
r
|
|
31
|
+
r"(?:\.(?P<tld>[a-z0-9\u00a1-\uffff]{2,}))"
|
|
32
|
+
r")"
|
|
33
33
|
# port number
|
|
34
|
-
r
|
|
34
|
+
r"(?::\d{2,5})?"
|
|
35
35
|
# resource path
|
|
36
|
-
r
|
|
36
|
+
r"(?:/\S*)?"
|
|
37
37
|
# query string
|
|
38
|
-
r
|
|
39
|
-
r
|
|
40
|
-
re.UNICODE | re.IGNORECASE
|
|
38
|
+
r"(?:\?\S*)?"
|
|
39
|
+
r"$",
|
|
40
|
+
re.UNICODE | re.IGNORECASE,
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
|
|
44
44
|
class ValidationError(ValueError):
|
|
45
|
-
|
|
45
|
+
"""Raised when URL is invalid"""
|
|
46
|
+
|
|
46
47
|
pass
|
|
47
48
|
|
|
48
49
|
|
|
@@ -74,59 +75,57 @@ def endpoint_for(endpoint, fallback_endpoint=None, **values):
|
|
|
74
75
|
return None
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
|
|
78
78
|
def idna(string):
|
|
79
|
-
return string.encode(
|
|
79
|
+
return string.encode("idna").decode("utf8")
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
def validate(url, schemes=None, tlds=None, private=None, local=None,
|
|
83
|
-
|
|
84
|
-
'''
|
|
82
|
+
def validate(url, schemes=None, tlds=None, private=None, local=None, credentials=None):
|
|
83
|
+
"""
|
|
85
84
|
Validate and normalize an URL
|
|
86
85
|
|
|
87
86
|
:param str url: The URL to validate and normalize
|
|
88
87
|
:return str: The normalized URL
|
|
89
88
|
:raises ValidationError: when URL does not validate
|
|
90
|
-
|
|
89
|
+
"""
|
|
91
90
|
url = url.strip()
|
|
92
91
|
|
|
93
|
-
private = config_for(private,
|
|
94
|
-
local = config_for(local,
|
|
95
|
-
credentials = config_for(credentials,
|
|
96
|
-
schemes = config_for(schemes,
|
|
97
|
-
tlds = config_for(tlds,
|
|
92
|
+
private = config_for(private, "URLS_ALLOW_PRIVATE")
|
|
93
|
+
local = config_for(local, "URLS_ALLOW_LOCAL")
|
|
94
|
+
credentials = config_for(credentials, "URLS_ALLOW_CREDENTIALS")
|
|
95
|
+
schemes = config_for(schemes, "URLS_ALLOWED_SCHEMES")
|
|
96
|
+
tlds = config_for(tlds, "URLS_ALLOWED_TLDS")
|
|
98
97
|
|
|
99
98
|
match = URL_REGEX.match(url)
|
|
100
99
|
if not match:
|
|
101
100
|
error(url)
|
|
102
101
|
|
|
103
|
-
scheme = (match.group(
|
|
102
|
+
scheme = (match.group("scheme") or "").lower()
|
|
104
103
|
if scheme and scheme not in schemes:
|
|
105
|
-
error(url, _(
|
|
104
|
+
error(url, _("Invalid scheme {0}, allowed schemes: {1}").format(scheme, ", ".join(schemes)))
|
|
106
105
|
|
|
107
|
-
if not credentials and match.group(
|
|
108
|
-
error(url, _(
|
|
106
|
+
if not credentials and match.group("credentials"):
|
|
107
|
+
error(url, _("Credentials in URL are not allowed"))
|
|
109
108
|
|
|
110
|
-
tld = match.group(
|
|
109
|
+
tld = match.group("tld")
|
|
111
110
|
if tld and tld not in tlds and idna(tld) not in tlds:
|
|
112
|
-
error(url, _(
|
|
111
|
+
error(url, _("Invalid TLD {0}").format(tld))
|
|
113
112
|
|
|
114
|
-
ip = match.group(
|
|
113
|
+
ip = match.group("ipv6") or match.group("ipv4")
|
|
115
114
|
if ip:
|
|
116
115
|
try:
|
|
117
116
|
ip = IPAddress(ip)
|
|
118
117
|
except AddrFormatError:
|
|
119
118
|
error(url)
|
|
120
119
|
if ip.is_multicast():
|
|
121
|
-
error(url, _(
|
|
120
|
+
error(url, _("{0} is a multicast IP").format(ip))
|
|
122
121
|
elif not ip.is_loopback() and ip.is_hostmask() or ip.is_netmask():
|
|
123
|
-
error(url, _(
|
|
122
|
+
error(url, _("{0} is a mask IP").format(ip))
|
|
124
123
|
|
|
125
124
|
if not local:
|
|
126
|
-
if ip and ip.is_loopback() or match.group(
|
|
127
|
-
error(url, _(
|
|
125
|
+
if ip and ip.is_loopback() or match.group("localhost"):
|
|
126
|
+
error(url, _("is a local URL"))
|
|
128
127
|
|
|
129
128
|
if not private and ip and ip.is_private():
|
|
130
|
-
error(url, _(
|
|
129
|
+
error(url, _("is a private URL"))
|
|
131
130
|
|
|
132
131
|
return url
|