udata 13.0.1.dev12__py3-none-any.whl → 14.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of udata might be problematic. Click here for more details.

Files changed (77) hide show
  1. udata/api/__init__.py +2 -8
  2. udata/app.py +12 -30
  3. udata/auth/forms.py +6 -4
  4. udata/commands/__init__.py +2 -14
  5. udata/commands/db.py +13 -25
  6. udata/commands/info.py +0 -16
  7. udata/core/avatars/api.py +43 -0
  8. udata/core/avatars/test_avatar_api.py +30 -0
  9. udata/core/dataservices/models.py +14 -2
  10. udata/core/dataset/tasks.py +36 -8
  11. udata/core/metrics/__init__.py +0 -6
  12. udata/core/site/models.py +2 -6
  13. udata/core/spatial/commands.py +2 -4
  14. udata/core/spatial/models.py +0 -10
  15. udata/core/spatial/tests/test_api.py +1 -36
  16. udata/core/user/models.py +10 -1
  17. udata/cors.py +2 -5
  18. udata/db/migrations.py +279 -0
  19. udata/frontend/__init__.py +3 -122
  20. udata/harvest/actions.py +3 -8
  21. udata/harvest/api.py +5 -14
  22. udata/harvest/backends/__init__.py +21 -9
  23. udata/harvest/backends/base.py +2 -2
  24. udata/harvest/backends/ckan/harvesters.py +2 -0
  25. udata/harvest/backends/dcat.py +3 -0
  26. udata/harvest/backends/maaf.py +1 -0
  27. udata/harvest/commands.py +6 -4
  28. udata/harvest/forms.py +9 -6
  29. udata/harvest/tasks.py +3 -5
  30. udata/harvest/tests/ckan/test_ckan_backend.py +2 -2
  31. udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
  32. udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
  33. udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
  34. udata/harvest/tests/dcat/udata.xml +6 -6
  35. udata/harvest/tests/factories.py +1 -1
  36. udata/harvest/tests/test_actions.py +5 -3
  37. udata/harvest/tests/test_api.py +2 -1
  38. udata/harvest/tests/test_base_backend.py +2 -0
  39. udata/harvest/tests/test_dcat_backend.py +3 -3
  40. udata/i18n.py +14 -273
  41. udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
  42. udata/models/__init__.py +0 -8
  43. udata/routing.py +0 -8
  44. udata/sentry.py +4 -10
  45. udata/settings.py +16 -17
  46. udata/tasks.py +3 -3
  47. udata/tests/__init__.py +1 -10
  48. udata/tests/api/test_dataservices_api.py +29 -1
  49. udata/tests/api/test_datasets_api.py +1 -2
  50. udata/tests/api/test_user_api.py +12 -0
  51. udata/tests/apiv2/test_topics.py +1 -1
  52. udata/tests/dataset/test_resource_preview.py +0 -1
  53. udata/tests/helpers.py +12 -0
  54. udata/tests/test_cors.py +1 -1
  55. udata/tests/test_migrations.py +181 -481
  56. udata/utils.py +5 -0
  57. {udata-13.0.1.dev12.dist-info → udata-14.0.0.dist-info}/METADATA +1 -2
  58. {udata-13.0.1.dev12.dist-info → udata-14.0.0.dist-info}/RECORD +62 -73
  59. {udata-13.0.1.dev12.dist-info → udata-14.0.0.dist-info}/entry_points.txt +3 -5
  60. udata/core/followers/views.py +0 -15
  61. udata/entrypoints.py +0 -93
  62. udata/features/identicon/__init__.py +0 -0
  63. udata/features/identicon/api.py +0 -13
  64. udata/features/identicon/backends.py +0 -131
  65. udata/features/identicon/tests/__init__.py +0 -0
  66. udata/features/identicon/tests/test_backends.py +0 -18
  67. udata/features/territories/__init__.py +0 -49
  68. udata/features/territories/api.py +0 -25
  69. udata/features/territories/models.py +0 -51
  70. udata/migrations/__init__.py +0 -367
  71. udata/tests/cli/test_db_cli.py +0 -68
  72. udata/tests/features/territories/__init__.py +0 -20
  73. udata/tests/features/territories/test_territories_api.py +0 -185
  74. udata/tests/frontend/test_hooks.py +0 -149
  75. {udata-13.0.1.dev12.dist-info → udata-14.0.0.dist-info}/WHEEL +0 -0
  76. {udata-13.0.1.dev12.dist-info → udata-14.0.0.dist-info}/licenses/LICENSE +0 -0
  77. {udata-13.0.1.dev12.dist-info → udata-14.0.0.dist-info}/top_level.txt +0 -0
udata/sentry.py CHANGED
@@ -2,15 +2,13 @@ import logging
2
2
  import re
3
3
  import warnings
4
4
 
5
- import pkg_resources
6
5
  from werkzeug.exceptions import HTTPException
7
6
 
8
- from udata import entrypoints
9
7
  from udata.core.storages.api import UploadProgress
8
+ from udata.utils import get_udata_version
10
9
 
11
10
  from .app import UDataApp
12
11
  from .auth import PermissionDenied
13
- from .frontend import package_version
14
12
 
15
13
  log = logging.getLogger(__name__)
16
14
 
@@ -64,7 +62,7 @@ def init_app(app: UDataApp):
64
62
  dsn=app.config["SENTRY_PUBLIC_DSN"],
65
63
  integrations=[FlaskIntegration(), CeleryIntegration()],
66
64
  ignore_errors=list(exceptions),
67
- release=f"udata@{package_version('udata')}",
65
+ release=f"udata@{get_udata_version()}",
68
66
  environment=app.config.get("SITE_ID", None),
69
67
  # Set traces_sample_rate to 1.0 to capture 100%
70
68
  # of transactions for performance monitoring.
@@ -83,9 +81,5 @@ def init_app(app: UDataApp):
83
81
  tags = app.config["SENTRY_TAGS"]
84
82
  for tag_key in tags:
85
83
  sentry_sdk.set_tag(tag_key, tags[tag_key])
86
- # Versions Management: uData and plugins versions as tags.
87
- for dist in entrypoints.get_plugins_dists(app):
88
- if dist.version:
89
- sentry_sdk.set_tag(dist.project_name, dist.version)
90
- # Do not forget udata itself
91
- sentry_sdk.set_tag("udata", pkg_resources.get_distribution("udata").version)
84
+
85
+ sentry_sdk.set_tag("udata", get_udata_version())
udata/settings.py CHANGED
@@ -21,7 +21,6 @@ class Defaults(object):
21
21
  DEFAULT_LANGUAGE = "en"
22
22
  SECRET_KEY = "Default uData secret key"
23
23
  CONTACT_EMAIL = "contact@example.org"
24
- TERRITORIES_EMAIL = "territories@example.org"
25
24
 
26
25
  CDATA_BASE_URL = None
27
26
 
@@ -175,7 +174,7 @@ class Defaults(object):
175
174
 
176
175
  UDATA_INSTANCE_NAME = "udata"
177
176
 
178
- PLUGINS = []
177
+ HARVESTER_BACKENDS = []
179
178
  THEME = None
180
179
 
181
180
  STATIC_DIRS = []
@@ -332,10 +331,6 @@ class Defaults(object):
332
331
  # - add inspire keyword during harvest if GEMETE INSPIRE thesaurus is used in DCAT.theme
333
332
  INSPIRE_SUPPORT = True
334
333
 
335
- ACTIVATE_TERRITORIES = False
336
- # The order is important to compute parents/children, smaller first.
337
- HANDLED_LEVELS = tuple()
338
-
339
334
  # Ignore some endpoint from API tracking
340
335
  # By default ignore the 3 most called APIs
341
336
  TRACKING_BLACKLIST = [
@@ -466,19 +461,22 @@ class Defaults(object):
466
461
  # if set to anything else than `None`
467
462
  ###########################################################################
468
463
  # avatar provider used to render user avatars
469
- AVATAR_PROVIDER = None
470
464
  # Number of blocks used by the internal provider
471
- AVATAR_INTERNAL_SIZE = None
465
+ AVATAR_INTERNAL_SIZE = 7
472
466
  # List of foreground colors used by the internal provider
473
- AVATAR_INTERNAL_FOREGROUND = None
467
+ AVATAR_INTERNAL_FOREGROUND = [
468
+ "rgb(45,79,255)",
469
+ "rgb(254,180,44)",
470
+ "rgb(226,121,234)",
471
+ "rgb(30,179,253)",
472
+ "rgb(232,77,65)",
473
+ "rgb(49,203,115)",
474
+ "rgb(141,69,170)",
475
+ ]
474
476
  # Background color used by the internal provider
475
- AVATAR_INTERNAL_BACKGROUND = None
477
+ AVATAR_INTERNAL_BACKGROUND = "rgb(224,224,224)"
476
478
  # Padding (in percent) used by the internal provider
477
- AVATAR_INTERNAL_PADDING = None
478
- # Skin (set) used by the robohash provider
479
- AVATAR_ROBOHASH_SKIN = None
480
- # The background used by the robohash provider.
481
- AVATAR_ROBOHASH_BACKGROUND = None
479
+ AVATAR_INTERNAL_PADDING = 10
482
480
 
483
481
  # Post settings
484
482
  ###########################################################################
@@ -530,6 +528,8 @@ class Defaults(object):
530
528
  "harvest",
531
529
  )
532
530
  EXPORT_CSV_DATASET_ID = None
531
+ EXPORT_CSV_ARCHIVE_S3_BUCKET = None # If this setting is set, an archive is uploaded to the corresponding S3 bucket every first day of the month (if export-csv is scheduled to run daily)
532
+ EXPORT_CSV_ARCHIVE_S3_FILENAME_PREFIX = "" # Useful to store the csv archives inside a subfolder of the bucket, ie setting 'csv-catalog-archives/'`
533
533
 
534
534
  # Autocomplete parameters
535
535
  #########################
@@ -636,7 +636,7 @@ class Testing(object):
636
636
  CELERY_TASK_ALWAYS_EAGER = True
637
637
  CELERY_TASK_EAGER_PROPAGATES = True
638
638
  TEST_WITH_PLUGINS = False
639
- PLUGINS = []
639
+ HARVESTER_BACKENDS = ["factory"]
640
640
  TEST_WITH_THEME = False
641
641
  THEME = "testing"
642
642
  CACHE_TYPE = "flask_caching.backends.null"
@@ -644,7 +644,6 @@ class Testing(object):
644
644
  DEBUG_TOOLBAR = False
645
645
  SERVER_NAME = "local.test"
646
646
  DEFAULT_LANGUAGE = "fr"
647
- ACTIVATE_TERRITORIES = False
648
647
  LOGGER_HANDLER_POLICY = "never"
649
648
  CELERYD_HIJACK_ROOT_LOGGER = False
650
649
  URLS_ALLOW_LOCAL = True # Test server URL is local.test
udata/tasks.py CHANGED
@@ -1,12 +1,11 @@
1
1
  import logging
2
+ from importlib.metadata import entry_points
2
3
  from urllib.parse import urlparse
3
4
 
4
5
  from celery import Celery, Task
5
6
  from celery.utils.log import get_task_logger
6
7
  from celerybeatmongo.schedulers import MongoScheduler
7
8
 
8
- from udata import entrypoints
9
-
10
9
  log = logging.getLogger(__name__)
11
10
 
12
11
 
@@ -176,6 +175,7 @@ def init_app(app):
176
175
  import udata.harvest.tasks # noqa
177
176
  import udata.db.tasks # noqa
178
177
 
179
- entrypoints.get_enabled("udata.tasks", app)
178
+ for ep in entry_points(group="udata.tasks"):
179
+ ep.load()
180
180
 
181
181
  return celery
udata/tests/__init__.py CHANGED
@@ -27,16 +27,7 @@ class TestCaseMixin:
27
27
  Not sure if the plugin situation is still relevant now that plugins are integrated
28
28
  into udata.
29
29
  """
30
- _settings = settings.Testing
31
- # apply the options(plugins) marker from pytest_flask as soon as app is created
32
- # https://github.com/pytest-dev/pytest-flask/blob/a62ea18cb0fe89e3f3911192ab9ea4f9b12f8a16/pytest_flask/plugin.py#L126
33
- # this lets us have default settings for plugins applied while testing
34
- plugins = getattr(_settings, "PLUGINS", [])
35
- for options in request.node.iter_markers("options"):
36
- option = options.kwargs.get("plugins", []) or options.kwargs.get("PLUGINS", [])
37
- plugins += option
38
- setattr(_settings, "PLUGINS", plugins)
39
- return _settings
30
+ return settings.Testing
40
31
 
41
32
  @pytest.fixture(autouse=True, name="app")
42
33
  def _app(self, request):
@@ -16,7 +16,7 @@ from udata.core.dataservices.models import Dataservice
16
16
  from udata.core.dataset.factories import DatasetFactory, LicenseFactory
17
17
  from udata.core.organization.factories import OrganizationFactory
18
18
  from udata.core.organization.models import Member
19
- from udata.core.topic.factories import TopicElementFactory, TopicFactory
19
+ from udata.core.topic.factories import ReuseFactory, TopicElementFactory, TopicFactory
20
20
  from udata.core.user.factories import AdminFactory, UserFactory
21
21
  from udata.i18n import gettext as _
22
22
  from udata.tests.helpers import assert200, assert400, assert410
@@ -99,6 +99,34 @@ class DataserviceAPITest(APITestCase):
99
99
  assert len(response.json["data"]) == 1
100
100
  assert response.json["data"][0]["id"] == str(topic_dataservice.id)
101
101
 
102
+ # filter on reuse
103
+ reuse_dataservice = DataserviceFactory()
104
+ reuse = ReuseFactory(dataservices=[reuse_dataservice])
105
+ response = self.get(url_for("api.dataservices", reuse=reuse.id))
106
+ assert200(response)
107
+ assert len(response.json["data"]) == 1
108
+ assert response.json["data"][0]["id"] == str(reuse_dataservice.id)
109
+
110
+ def test_dataservices_topic_filter_errors(self):
111
+ # non-existing
112
+ response = self.get(url_for("api.dataservices", topic="690c7f48ec85adaa376c1e93"))
113
+ assert200(response)
114
+
115
+ # not an object ID
116
+ response = self.get(url_for("api.dataservices", topic="xxx"))
117
+ assert400(response)
118
+ assert "`topic` must be an identifier" in response.json["message"]
119
+
120
+ def test_dataservices_reuse_filter_errors(self):
121
+ # non-existing
122
+ response = self.get(url_for("api.dataservices", reuse="690c7f48ec85adaa376c1e93"))
123
+ assert200(response)
124
+
125
+ # not an object ID
126
+ response = self.get(url_for("api.dataservices", reuse="xxx"))
127
+ assert400(response)
128
+ assert "`reuse` must be an identifier" in response.json["message"]
129
+
102
130
  def test_dataservice_api_create(self):
103
131
  user = self.login()
104
132
  datasets = DatasetFactory.create_batch(3)
@@ -49,8 +49,7 @@ from udata.core.user.factories import AdminFactory, UserFactory
49
49
  from udata.i18n import gettext as _
50
50
  from udata.models import CommunityResource, Dataset, Follow, Member, db
51
51
  from udata.tags import TAG_MAX_LENGTH, TAG_MIN_LENGTH
52
- from udata.tests.features.territories import create_geozones_fixtures
53
- from udata.tests.helpers import assert200, assert404
52
+ from udata.tests.helpers import assert200, assert404, create_geozones_fixtures
54
53
  from udata.utils import faker, unique_string
55
54
 
56
55
  from . import APITestCase, PytestOnlyAPITestCase
@@ -175,6 +175,18 @@ class UserAPITest(APITestCase):
175
175
 
176
176
  self.assertEqual(len(response.json["data"]), 2)
177
177
 
178
+ def test_user_api_full_text_search_email(self):
179
+ """It should find users based on last name"""
180
+ self.login(AdminFactory())
181
+
182
+ UserFactory(email="john@example.org")
183
+ UserFactory(email="jane@example.org")
184
+
185
+ response = self.get(url_for("api.users", q="jane"))
186
+ self.assert200(response)
187
+
188
+ self.assertEqual(len(response.json["data"]), 1)
189
+
178
190
  def test_user_api_full_text_search_unicode(self):
179
191
  """It should find user with special characters"""
180
192
  self.login(AdminFactory())
@@ -23,7 +23,7 @@ from udata.core.user.factories import UserFactory
23
23
  from udata.i18n import _
24
24
  from udata.tests.api import APITestCase
25
25
  from udata.tests.api.test_datasets_api import SAMPLE_GEOM
26
- from udata.tests.features.territories import create_geozones_fixtures
26
+ from udata.tests.helpers import create_geozones_fixtures
27
27
 
28
28
 
29
29
  class TopicsListAPITest(APITestCase):
@@ -10,7 +10,6 @@ DUMMY_EXTRAS = {
10
10
  MAX_SIZE = 50000
11
11
 
12
12
 
13
- @pytest.mark.options(PLUGINS=["tabular"])
14
13
  class ResourcePreviewTest(PytestOnlyAPITestCase):
15
14
  def expected_url(self, rid):
16
15
  return "http://preview.me/resources/{0}".format(rid)
udata/tests/helpers.py CHANGED
@@ -9,6 +9,7 @@ from flask import current_app, json
9
9
  from flask_security.babel import FsDomain
10
10
  from PIL import Image
11
11
 
12
+ from udata.core.spatial.factories import GeoZoneFactory
12
13
  from udata.mail import mail_sent
13
14
 
14
15
 
@@ -221,5 +222,16 @@ def create_test_image():
221
222
  return file
222
223
 
223
224
 
225
+ def create_geozones_fixtures():
226
+ paca = GeoZoneFactory(
227
+ id="fr:region:93", level="fr:region", name="Provence Alpes Côtes dAzur", code="93"
228
+ )
229
+ bdr = GeoZoneFactory(
230
+ id="fr:departement:13", level="fr:departement", name="Bouches-du-Rhône", code="13"
231
+ )
232
+ arles = GeoZoneFactory(id="fr:commune:13004", level="fr:commune", name="Arles", code="13004")
233
+ return paca, bdr, arles
234
+
235
+
224
236
  def security_gettext(string):
225
237
  return FsDomain(current_app).gettext(string)
udata/tests/test_cors.py CHANGED
@@ -27,7 +27,7 @@ class CorsTest(APITestCase):
27
27
  assert "Access-Control-Allow-Origin" in response.headers
28
28
 
29
29
  # Resource permalink
30
- response = self.get(f"/fr/datasets/r/{dataset.resources[0].id}", headers=cors_headers)
30
+ response = self.get(f"/datasets/r/{dataset.resources[0].id}", headers=cors_headers)
31
31
  assert_status(response, 404) # The route is defined in udata-front
32
32
  assert "Access-Control-Allow-Origin" in response.headers
33
33