udata 13.0.1.dev10__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 (93) 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 -18
  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_security_api.py +2 -1
  51. udata/tests/api/test_user_api.py +12 -0
  52. udata/tests/apiv2/test_topics.py +1 -1
  53. udata/tests/dataset/test_resource_preview.py +0 -1
  54. udata/tests/helpers.py +12 -0
  55. udata/tests/test_cors.py +1 -1
  56. udata/tests/test_mail.py +2 -2
  57. udata/tests/test_migrations.py +181 -481
  58. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  59. udata/translations/ar/LC_MESSAGES/udata.po +267 -279
  60. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  61. udata/translations/de/LC_MESSAGES/udata.po +269 -281
  62. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  63. udata/translations/es/LC_MESSAGES/udata.po +267 -279
  64. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  65. udata/translations/fr/LC_MESSAGES/udata.po +278 -290
  66. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  67. udata/translations/it/LC_MESSAGES/udata.po +269 -281
  68. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  69. udata/translations/pt/LC_MESSAGES/udata.po +269 -281
  70. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  71. udata/translations/sr/LC_MESSAGES/udata.po +270 -282
  72. udata/utils.py +5 -0
  73. {udata-13.0.1.dev10.dist-info → udata-14.0.0.dist-info}/METADATA +1 -3
  74. {udata-13.0.1.dev10.dist-info → udata-14.0.0.dist-info}/RECORD +78 -89
  75. {udata-13.0.1.dev10.dist-info → udata-14.0.0.dist-info}/entry_points.txt +3 -5
  76. udata/core/followers/views.py +0 -15
  77. udata/entrypoints.py +0 -94
  78. udata/features/identicon/__init__.py +0 -0
  79. udata/features/identicon/api.py +0 -13
  80. udata/features/identicon/backends.py +0 -131
  81. udata/features/identicon/tests/__init__.py +0 -0
  82. udata/features/identicon/tests/test_backends.py +0 -18
  83. udata/features/territories/__init__.py +0 -49
  84. udata/features/territories/api.py +0 -25
  85. udata/features/territories/models.py +0 -51
  86. udata/migrations/__init__.py +0 -367
  87. udata/tests/cli/test_db_cli.py +0 -68
  88. udata/tests/features/territories/__init__.py +0 -20
  89. udata/tests/features/territories/test_territories_api.py +0 -185
  90. udata/tests/frontend/test_hooks.py +0 -149
  91. {udata-13.0.1.dev10.dist-info → udata-14.0.0.dist-info}/WHEEL +0 -0
  92. {udata-13.0.1.dev10.dist-info → udata-14.0.0.dist-info}/licenses/LICENSE +0 -0
  93. {udata-13.0.1.dev10.dist-info → udata-14.0.0.dist-info}/top_level.txt +0 -0
udata/harvest/forms.py CHANGED
@@ -1,8 +1,8 @@
1
1
  from udata.forms import Form, fields, validators
2
+ from udata.harvest.backends import get_backend, get_enabled_backends
2
3
  from udata.i18n import lazy_gettext as _
3
4
  from udata.utils import safe_unicode
4
5
 
5
- from .actions import list_backends
6
6
  from .models import VALIDATION_REFUSED, VALIDATION_STATES
7
7
 
8
8
  __all__ = "HarvestSourceForm", "HarvestSourceValidationForm"
@@ -13,9 +13,6 @@ class HarvestConfigField(fields.DictField):
13
13
  A DictField with extras validations on known configurations
14
14
  """
15
15
 
16
- def get_backend(self, form):
17
- return next(b for b in list_backends() if b.name == form.backend.data)
18
-
19
16
  def get_filter_specs(self, backend, key):
20
17
  candidates = (f for f in backend.filters if f.key == key)
21
18
  return next(candidates, None)
@@ -30,7 +27,10 @@ class HarvestConfigField(fields.DictField):
30
27
 
31
28
  def pre_validate(self, form):
32
29
  if self.data:
33
- backend = self.get_backend(form)
30
+ backend = get_backend(form.backend.data)
31
+ if backend is None:
32
+ return # Should have been catch by the enum check for `form.backend`
33
+
34
34
  # Validate filters
35
35
  for f in self.data.get("filters") or []:
36
36
  if not ("key" in f and "value" in f):
@@ -49,6 +49,7 @@ class HarvestConfigField(fields.DictField):
49
49
  msg = '"{0}" filter should of type "{1}"'
50
50
  msg = msg.format(specs.key, specs.type.__name__)
51
51
  raise validators.ValidationError(msg)
52
+
52
53
  # Validate extras configs
53
54
  for f in self.data.get("extra_configs") or []:
54
55
  if not ("key" in f and "value" in f):
@@ -63,6 +64,7 @@ class HarvestConfigField(fields.DictField):
63
64
  msg = '"{0}" extra config should be of type "{1}"'
64
65
  msg = msg.format(specs.key, specs.type.__name__)
65
66
  raise validators.ValidationError(msg)
67
+
66
68
  # Validate features
67
69
  for key, value in (self.data.get("features") or {}).items():
68
70
  if not isinstance(value, bool):
@@ -81,7 +83,8 @@ class HarvestSourceForm(Form):
81
83
  )
82
84
  url = fields.URLField(_("URL"), [validators.DataRequired()])
83
85
  backend = fields.SelectField(
84
- _("Backend"), choices=lambda: [(b.name, b.display_name) for b in list_backends()]
86
+ _("Backend"),
87
+ choices=lambda: [(b.name, b.display_name) for b in get_enabled_backends().values()],
85
88
  )
86
89
  owner = fields.CurrentUserField()
87
90
  organization = fields.PublishAsField(_("Publish as"))
udata/harvest/tasks.py CHANGED
@@ -1,5 +1,3 @@
1
- from flask import current_app
2
-
3
1
  from udata.tasks import get_logger, job, task
4
2
 
5
3
  from . import backends
@@ -16,7 +14,7 @@ def harvest(self, ident):
16
14
  if source.deleted or not source.active:
17
15
  log.info('Ignoring inactive or deleted source "%s"', source.id)
18
16
  return # Ignore deleted and inactive sources
19
- Backend = backends.get(current_app, source.backend)
17
+ Backend = backends.get_backend(source.backend)
20
18
  backend = Backend(source)
21
19
 
22
20
  backend.harvest()
@@ -27,7 +25,7 @@ def harvest_job_item(job_id, item_id):
27
25
  log.info('Harvesting item %s for job "%s"', item_id, job_id)
28
26
 
29
27
  job = HarvestJob.objects.get(pk=job_id)
30
- Backend = backends.get(current_app, job.source.backend)
28
+ Backend = backends.get_backend(job.source.backend)
31
29
  backend = Backend(job)
32
30
 
33
31
  item = next(i for i in job.items if i.remote_id == item_id)
@@ -40,7 +38,7 @@ def harvest_job_item(job_id, item_id):
40
38
  def harvest_job_finalize(results, job_id):
41
39
  log.info('Finalize harvesting for job "%s"', job_id)
42
40
  job = HarvestJob.objects.get(pk=job_id)
43
- Backend = backends.get(current_app, job.source.backend)
41
+ Backend = backends.get_backend(job.source.backend)
44
42
  backend = Backend(job)
45
43
  backend.finalize()
46
44
 
@@ -364,7 +364,7 @@ def empty_extras(resource_data):
364
364
  ##############################################################################
365
365
 
366
366
 
367
- @pytest.mark.options(PLUGINS=["ckan"])
367
+ @pytest.mark.options(HARVESTER_BACKENDS=["ckan"])
368
368
  class CkanBackendTest(PytestOnlyDBTestCase):
369
369
  @pytest.mark.ckan_data("minimal")
370
370
  def test_minimal_metadata(self, data, result, kwargs):
@@ -505,7 +505,7 @@ class CkanBackendTest(PytestOnlyDBTestCase):
505
505
  ##############################################################################
506
506
 
507
507
 
508
- @pytest.mark.options(PLUGINS=["ckan"])
508
+ @pytest.mark.options(HARVESTER_BACKENDS=["ckan"])
509
509
  class CkanBackendEdgeCasesTest(PytestOnlyDBTestCase):
510
510
  def test_minimal_ckan_response(self, rmock):
511
511
  """CKAN Harvester should accept the minimum dataset payload"""
@@ -13,7 +13,7 @@ API_URL = "{}api/3/action/package_list".format(CKAN_URL)
13
13
  STATUS_CODE = (400, 500)
14
14
 
15
15
 
16
- @pytest.mark.options(PLUGINS=["ckan"])
16
+ @pytest.mark.options(HARVESTER_BACKENDS=["ckan"])
17
17
  class CkanBackendErrorsTest(PytestOnlyDBTestCase):
18
18
  @pytest.mark.parametrize("code", STATUS_CODE)
19
19
  def test_html_error(self, rmock, code):
@@ -8,7 +8,7 @@ from udata.tests.api import PytestOnlyDBTestCase
8
8
  from udata.utils import faker
9
9
 
10
10
 
11
- @pytest.mark.options(PLUGINS=["ckan"])
11
+ @pytest.mark.options(HARVESTER_BACKENDS=["ckan"])
12
12
  class CkanBackendFilterTest(PytestOnlyDBTestCase):
13
13
  def test_include_org_filter(self, ckan, rmock):
14
14
  source = HarvestSourceFactory(
@@ -16,7 +16,7 @@ def data_path(filename):
16
16
  return os.path.join(os.path.dirname(__file__), "data", filename)
17
17
 
18
18
 
19
- @pytest.mark.options(PLUGINS=["dkan"])
19
+ @pytest.mark.options(HARVESTER_BACKENDS=["dkan"])
20
20
  class DkanBackendTest(PytestOnlyDBTestCase):
21
21
  def test_dkan_french_w_license(self, rmock):
22
22
  """CKAN Harvester should accept the minimum dataset payload"""
@@ -37,7 +37,7 @@
37
37
  <dct:title>bureau-de-vote-vanves.csv</dct:title>
38
38
  <dct:description>Bureaux de vote - Vanves (csv)</dct:description>
39
39
  <dcat:downloadURL rdf:resource="https://vanves-seineouest.opendatasoft.com/api/explore/v2.1/catalog/datasets/bureau-de-vote-vanves/exports/csv?use_labels=true"/>
40
- <dcat:accessURL rdf:resource="https://www.data.gouv.fr/fr/datasets/r/5dd4e0b2-4d96-4f36-b73e-b78ec993703c"/>
40
+ <dcat:accessURL rdf:resource="https://www.data.gouv.fr/datasets/r/5dd4e0b2-4d96-4f36-b73e-b78ec993703c"/>
41
41
  <dct:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:issued>
42
42
  <dct:modified rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:modified>
43
43
  <dct:rights>License Not Specified</dct:rights>
@@ -62,7 +62,7 @@
62
62
  <dct:title>bureau-de-vote-vanves.geojson</dct:title>
63
63
  <dct:description>Bureaux de vote - Vanves (geojson)</dct:description>
64
64
  <dcat:downloadURL rdf:resource="https://vanves-seineouest.opendatasoft.com/api/explore/v2.1/catalog/datasets/bureau-de-vote-vanves/exports/geojson"/>
65
- <dcat:accessURL rdf:resource="https://www.data.gouv.fr/fr/datasets/r/d78d1245-6e8e-44d8-88cd-87b1dd66034f"/>
65
+ <dcat:accessURL rdf:resource="https://www.data.gouv.fr/datasets/r/d78d1245-6e8e-44d8-88cd-87b1dd66034f"/>
66
66
  <dct:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:issued>
67
67
  <dct:modified rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:modified>
68
68
  <dct:rights>License Not Specified</dct:rights>
@@ -86,7 +86,7 @@
86
86
  <dct:title>vfe_public_219200755_20240506.json</dct:title>
87
87
  <dct:description>Ville de Vanves - Part des véhicules à faibles émissions dans le renouvellement du parc (json)</dct:description>
88
88
  <dcat:downloadURL rdf:resource="https://vanves-seineouest.opendatasoft.com/api/explore/v2.1/catalog/datasets/vfe_public_219200755_20240506/exports/json"/>
89
- <dcat:accessURL rdf:resource="https://www.data.gouv.fr/fr/datasets/r/482677b8-379c-45a5-83ef-0361fecb4cc3"/>
89
+ <dcat:accessURL rdf:resource="https://www.data.gouv.fr/datasets/r/482677b8-379c-45a5-83ef-0361fecb4cc3"/>
90
90
  <dct:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2024-05-06T15:15:18</dct:issued>
91
91
  <dct:modified rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2024-05-06T15:15:18</dct:modified>
92
92
  <dct:rights>License Not Specified</dct:rights>
@@ -99,7 +99,7 @@
99
99
  <dct:title>bureau-de-vote-vanves.zip</dct:title>
100
100
  <dct:description>Bureaux de vote - Vanves (shp)</dct:description>
101
101
  <dcat:downloadURL rdf:resource="https://vanves-seineouest.opendatasoft.com/api/explore/v2.1/catalog/datasets/bureau-de-vote-vanves/exports/shp"/>
102
- <dcat:accessURL rdf:resource="https://www.data.gouv.fr/fr/datasets/r/b18fb2bd-6c8b-47a8-84fb-cdc8e29f4f1a"/>
102
+ <dcat:accessURL rdf:resource="https://www.data.gouv.fr/datasets/r/b18fb2bd-6c8b-47a8-84fb-cdc8e29f4f1a"/>
103
103
  <dct:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:issued>
104
104
  <dct:modified rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:modified>
105
105
  <dct:rights>License Not Specified</dct:rights>
@@ -112,7 +112,7 @@
112
112
  <dct:title>vfe_public_219200755_20240506.csv</dct:title>
113
113
  <dct:description>Ville de Vanves - Part des véhicules à faibles émissions dans le renouvellement du parc (csv)</dct:description>
114
114
  <dcat:downloadURL rdf:resource="https://vanves-seineouest.opendatasoft.com/api/explore/v2.1/catalog/datasets/vfe_public_219200755_20240506/exports/csv?use_labels=true"/>
115
- <dcat:accessURL rdf:resource="https://www.data.gouv.fr/fr/datasets/r/aab4d337-a617-4c18-a045-291d68787a7d"/>
115
+ <dcat:accessURL rdf:resource="https://www.data.gouv.fr/datasets/r/aab4d337-a617-4c18-a045-291d68787a7d"/>
116
116
  <dct:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2024-05-06T15:15:18</dct:issued>
117
117
  <dct:modified rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2024-05-06T15:15:18</dct:modified>
118
118
  <dct:rights>License Not Specified</dct:rights>
@@ -161,7 +161,7 @@
161
161
  <dct:title>bureau-de-vote-vanves.json</dct:title>
162
162
  <dct:description>Bureaux de vote - Vanves (json)</dct:description>
163
163
  <dcat:downloadURL rdf:resource="https://vanves-seineouest.opendatasoft.com/api/explore/v2.1/catalog/datasets/bureau-de-vote-vanves/exports/json"/>
164
- <dcat:accessURL rdf:resource="https://www.data.gouv.fr/fr/datasets/r/0f80d285-72f8-49f8-a691-1dad6bd2f6db"/>
164
+ <dcat:accessURL rdf:resource="https://www.data.gouv.fr/datasets/r/0f80d285-72f8-49f8-a691-1dad6bd2f6db"/>
165
165
  <dct:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:issued>
166
166
  <dct:modified rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2019-04-19T12:21:56</dct:modified>
167
167
  <dct:rights>License Not Specified</dct:rights>
@@ -79,4 +79,4 @@ class MockBackendsMixin(object):
79
79
  @pytest.fixture(autouse=True)
80
80
  def mock_backend(self, mocker):
81
81
  return_value = {"factory": FactoryBackend}
82
- mocker.patch("udata.harvest.backends.get_all", return_value=return_value)
82
+ mocker.patch("udata.harvest.backends.get_all_backends", return_value=return_value)
@@ -15,13 +15,14 @@ from udata.core.dataset.factories import DatasetFactory
15
15
  from udata.core.dataset.models import HarvestDatasetMetadata
16
16
  from udata.core.organization.factories import OrganizationFactory
17
17
  from udata.core.user.factories import UserFactory
18
+ from udata.harvest.backends import get_enabled_backends
19
+ from udata.harvest.backends.base import BaseBackend
18
20
  from udata.models import Dataset, PeriodicTask
19
21
  from udata.tests.api import PytestOnlyDBTestCase
20
22
  from udata.tests.helpers import assert_emit, assert_equal_dates, assert_not_emit
21
23
  from udata.utils import faker
22
24
 
23
25
  from .. import actions, signals
24
- from ..backends import BaseBackend
25
26
  from ..models import (
26
27
  VALIDATION_ACCEPTED,
27
28
  VALIDATION_PENDING,
@@ -42,9 +43,10 @@ from .factories import (
42
43
  log = logging.getLogger(__name__)
43
44
 
44
45
 
45
- class HarvestActionsTest(PytestOnlyDBTestCase):
46
+ class HarvestActionsTest(MockBackendsMixin, PytestOnlyDBTestCase):
46
47
  def test_list_backends(self):
47
- for backend in actions.list_backends():
48
+ assert len(get_enabled_backends()) > 0
49
+ for backend in get_enabled_backends().values():
48
50
  assert issubclass(backend, BaseBackend)
49
51
 
50
52
  def test_list_sources(self):
@@ -7,6 +7,7 @@ from pytest_mock import MockerFixture
7
7
 
8
8
  from udata.core.organization.factories import OrganizationFactory
9
9
  from udata.core.user.factories import AdminFactory, UserFactory
10
+ from udata.harvest.backends import get_enabled_backends
10
11
  from udata.models import Member, PeriodicTask
11
12
  from udata.tests.api import PytestOnlyAPITestCase
12
13
  from udata.tests.helpers import assert200, assert201, assert204, assert400, assert403, assert404
@@ -31,7 +32,7 @@ class HarvestAPITest(MockBackendsMixin, PytestOnlyAPITestCase):
31
32
  """It should fetch the harvest backends list from the API"""
32
33
  response = api.get(url_for("api.harvest_backends"))
33
34
  assert200(response)
34
- assert len(response.json) == len(actions.list_backends())
35
+ assert len(response.json) == len(get_enabled_backends())
35
36
  for data in response.json:
36
37
  assert "id" in data
37
38
  assert "label" in data
@@ -29,6 +29,8 @@ def gen_remote_IDs(num: int, prefix: str = "") -> list[str]:
29
29
 
30
30
 
31
31
  class FakeBackend(BaseBackend):
32
+ name = "fake-backend"
33
+ display_name = "Fake Backend"
32
34
  filters = (
33
35
  HarvestFilter("First filter", "first", str),
34
36
  HarvestFilter("Second filter", "second", str),
@@ -68,7 +68,7 @@ def mock_csw_pagination(rmock, path, pattern):
68
68
  return url
69
69
 
70
70
 
71
- @pytest.mark.options(PLUGINS=["dcat"])
71
+ @pytest.mark.options(HARVESTER_BACKENDS=["dcat"])
72
72
  class DcatBackendTest(PytestOnlyDBTestCase):
73
73
  def test_simple_flat(self, rmock):
74
74
  filename = "flat.jsonld"
@@ -926,7 +926,7 @@ class DcatBackendTest(PytestOnlyDBTestCase):
926
926
  assert "404 Client Error" in job.errors[0].message
927
927
 
928
928
 
929
- @pytest.mark.options(PLUGINS=["csw"])
929
+ @pytest.mark.options(HARVESTER_BACKENDS=["csw*"])
930
930
  class CswDcatBackendTest(PytestOnlyDBTestCase):
931
931
  def test_geonetworkv4(self, rmock):
932
932
  url = mock_csw_pagination(rmock, "geonetwork/srv/eng/csw.rdf", "geonetworkv4-page-{}.xml")
@@ -1076,7 +1076,7 @@ class CswDcatBackendTest(PytestOnlyDBTestCase):
1076
1076
  assert len(job.items) == 1
1077
1077
 
1078
1078
 
1079
- @pytest.mark.options(PLUGINS=["csw"])
1079
+ @pytest.mark.options(HARVESTER_BACKENDS=["csw*"])
1080
1080
  class CswIso19139DcatBackendTest(PytestOnlyDBTestCase):
1081
1081
  @pytest.mark.parametrize(
1082
1082
  "remote_url_prefix",
udata/i18n.py CHANGED
@@ -1,49 +1,29 @@
1
- import importlib.util
2
1
  from contextlib import contextmanager
3
- from datetime import datetime
4
- from glob import iglob
5
- from os.path import basename, dirname, join
2
+ from importlib import resources
3
+ from importlib.metadata import entry_points
6
4
 
7
5
  import flask_babel
8
- from babel.dates import format_timedelta as babel_format_timedelta
9
- from flask import ( # noqa
10
- abort,
11
- current_app,
12
- g,
13
- has_request_context,
14
- redirect,
15
- request,
16
- url_for,
17
- )
18
- from flask.blueprints import BlueprintSetupState, _endpoint_from_view_func
19
- from flask_babel import Babel, format_date, format_datetime, refresh # noqa
20
- from flask_babel import get_locale as get_current_locale # noqa
6
+ from flask import current_app, g, request
7
+ from flask_babel import Babel, refresh
8
+ from flask_login import current_user
21
9
  from werkzeug.local import LocalProxy
22
10
 
23
- from udata import entrypoints
24
11
  from udata.app import Blueprint
25
- from udata.auth import current_user
26
12
  from udata.errors import ConfigError
27
- from udata.utils import multi_to_dict
28
13
 
29
14
 
30
15
  def get_translation_directories_and_domains():
31
- translations_dir = []
16
+ translations_dirs = []
32
17
  domains = []
33
18
 
34
- # udata and plugin translations
35
- for pkg in entrypoints.get_roots(current_app):
36
- spec = importlib.util.find_spec(pkg)
37
- path = dirname(spec.origin)
38
- plugin_domains = [
39
- f.replace(path, "").replace(".pot", "")[1:]
40
- for f in iglob(join(path, "**/translations/*.pot"), recursive=True)
41
- ]
42
- for domain in plugin_domains:
43
- translations_dir.append(join(path, dirname(domain)))
44
- domains.append(basename(domain))
19
+ for pkg in entry_points(group="udata.i18n"):
20
+ module = pkg.load()
21
+ path = resources.files(module)
22
+ # `/ ""` is here to transform MultiplexedPath to a simple str
23
+ translations_dirs.append(str(path / ""))
24
+ domains.append(pkg.name)
45
25
 
46
- return translations_dir, domains
26
+ return translations_dirs, domains
47
27
 
48
28
 
49
29
  def get_locale():
@@ -89,22 +69,6 @@ def lazy_pgettext(*args, **kwargs):
89
69
  return flask_babel.lazy_pgettext(*args, **kwargs)
90
70
 
91
71
 
92
- def format_timedelta(
93
- datetime_or_timedelta, granularity="second", add_direction=False, threshold=0.85
94
- ):
95
- """This is format_timedelta from Flask-Babel"""
96
- """Flask-BabelEx missed the add_direction parameter"""
97
- if isinstance(datetime_or_timedelta, datetime):
98
- datetime_or_timedelta = datetime.utcnow() - datetime_or_timedelta
99
- return babel_format_timedelta(
100
- datetime_or_timedelta,
101
- granularity,
102
- threshold=threshold,
103
- add_direction=add_direction,
104
- locale=get_current_locale(),
105
- )
106
-
107
-
108
72
  def _default_lang(user=None):
109
73
  lang = getattr(user or current_user, "prefered_language", None)
110
74
  return lang or current_app.config["DEFAULT_LANGUAGE"]
@@ -152,228 +116,5 @@ def init_app(app):
152
116
  )
153
117
 
154
118
 
155
- def _add_language_code(endpoint, values):
156
- try:
157
- if current_app.url_map.is_endpoint_expecting(endpoint, "lang_code"):
158
- values.setdefault("lang_code", g.get("lang_code") or get_locale())
159
- except KeyError: # Endpoint does not exist
160
- pass
161
-
162
-
163
- def _pull_lang_code(endpoint, values):
164
- lang_code = values.pop("lang_code", g.get("lang_code") or get_locale())
165
- if lang_code not in current_app.config["LANGUAGES"]:
166
- abort(redirect(url_for(endpoint, lang_code=default_lang, **values)))
167
- g.lang_code = lang_code
168
-
169
-
170
- def redirect_to_lang(*args, **kwargs):
171
- """Redirect non lang-prefixed urls to default language."""
172
- endpoint = request.endpoint.replace("_redirect", "")
173
- kwargs = multi_to_dict(request.args)
174
- kwargs.update(request.view_args)
175
- kwargs["lang_code"] = default_lang
176
- return redirect(url_for(endpoint, **kwargs))
177
-
178
-
179
- def redirect_to_unlocalized(*args, **kwargs):
180
- """Redirect lang-prefixed urls to no prefixed URL."""
181
- endpoint = request.endpoint.replace("_redirect", "")
182
- kwargs = multi_to_dict(request.args)
183
- kwargs.update(request.view_args)
184
- kwargs.pop("lang_code", None)
185
- return redirect(url_for(endpoint, **kwargs))
186
-
187
-
188
- class I18nBlueprintSetupState(BlueprintSetupState):
189
- def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
190
- """A helper method to register a rule (and optionally a view function)
191
- to the application. The endpoint is automatically prefixed with the
192
- blueprint's name.
193
- The URL rule is registered twice.
194
- """
195
- # Static assets are not localized
196
- if endpoint == "static":
197
- return super(I18nBlueprintSetupState, self).add_url_rule(
198
- rule, endpoint=endpoint, view_func=view_func, **options
199
- )
200
- if self.url_prefix:
201
- rule = self.url_prefix + rule
202
- options.setdefault("subdomain", self.subdomain)
203
- if endpoint is None:
204
- endpoint = _endpoint_from_view_func(view_func)
205
- defaults = self.url_defaults
206
- if "defaults" in options:
207
- defaults = dict(defaults, **options.pop("defaults"))
208
-
209
- self.app.add_url_rule(
210
- rule,
211
- "%s.%s" % (self.blueprint.name, endpoint),
212
- view_func,
213
- defaults=defaults,
214
- **options,
215
- )
216
-
217
-
218
119
  class I18nBlueprint(Blueprint):
219
- def make_setup_state(self, app, options, first_registration=False):
220
- return I18nBlueprintSetupState(self, app, options, first_registration)
221
-
222
- def register(self, *args, **kwargs):
223
- self.url_defaults(_add_language_code)
224
- self.url_value_preprocessor(_pull_lang_code)
225
- super(I18nBlueprint, self).register(*args, **kwargs)
226
-
227
-
228
- ISO_639_1_CODES = (
229
- "aa",
230
- "ab",
231
- "af",
232
- "am",
233
- "an",
234
- "ar",
235
- "as",
236
- "ay",
237
- "az",
238
- "ba",
239
- "be",
240
- "bg",
241
- "bh",
242
- "bi",
243
- "bn",
244
- "bo",
245
- "br",
246
- "ca",
247
- "co",
248
- "cs",
249
- "cy",
250
- "da",
251
- "de",
252
- "dz",
253
- "el",
254
- "en",
255
- "eo",
256
- "es",
257
- "et",
258
- "eu",
259
- "fa",
260
- "fi",
261
- "fj",
262
- "fo",
263
- "fr",
264
- "fy",
265
- "ga",
266
- "gd",
267
- "gl",
268
- "gn",
269
- "gu",
270
- "gv",
271
- "ha",
272
- "he",
273
- "hi",
274
- "hr",
275
- "ht",
276
- "hu",
277
- "hy",
278
- "ia",
279
- "id",
280
- "ie",
281
- "ii",
282
- "ik",
283
- "in",
284
- "io",
285
- "is",
286
- "it",
287
- "iu",
288
- "iw",
289
- "ja",
290
- "ji",
291
- "jv",
292
- "ka",
293
- "kk",
294
- "kl",
295
- "km",
296
- "kn",
297
- "ko",
298
- "ks",
299
- "ku",
300
- "ky",
301
- "la",
302
- "li",
303
- "ln",
304
- "lo",
305
- "lt",
306
- "lv",
307
- "mg",
308
- "mi",
309
- "mk",
310
- "ml",
311
- "mn",
312
- "mo",
313
- "mr",
314
- "ms",
315
- "mt",
316
- "my",
317
- "na",
318
- "ne",
319
- "nl",
320
- "no",
321
- "oc",
322
- "om",
323
- "or",
324
- "pa",
325
- "pl",
326
- "ps",
327
- "pt",
328
- "qu",
329
- "rm",
330
- "rn",
331
- "ro",
332
- "ru",
333
- "rw",
334
- "sa",
335
- "sd",
336
- "sg",
337
- "sh",
338
- "si",
339
- "sk",
340
- "sl",
341
- "sm",
342
- "sn",
343
- "so",
344
- "sq",
345
- "sr",
346
- "ss",
347
- "st",
348
- "su",
349
- "sv",
350
- "sw",
351
- "ta",
352
- "te",
353
- "tg",
354
- "th",
355
- "ti",
356
- "tk",
357
- "tl",
358
- "tn",
359
- "to",
360
- "tr",
361
- "ts",
362
- "tt",
363
- "tw",
364
- "ug",
365
- "uk",
366
- "ur",
367
- "uz",
368
- "vi",
369
- "vo",
370
- "wa",
371
- "wo",
372
- "xh",
373
- "yi",
374
- "yo",
375
- "zh",
376
- "zh-Hans",
377
- "zh-Hant",
378
- "zu",
379
- )
120
+ pass
@@ -0,0 +1,25 @@
1
+ """Delete Topic index 'name_text'"""
2
+
3
+ import logging
4
+
5
+ from mongoengine.connection import get_db
6
+ from pymongo.errors import OperationFailure
7
+
8
+ from udata.models import User
9
+
10
+ log = logging.getLogger(__name__)
11
+
12
+
13
+ def migrate(db):
14
+ log.info("Deleting index…")
15
+
16
+ collection = get_db().user
17
+
18
+ # Remove previous index
19
+ try:
20
+ collection.drop_index("slug_text")
21
+ except OperationFailure:
22
+ log.info("Index `slug_text` does not exist?", exc_info=True)
23
+
24
+ # Force new index creation (before old code in workers recreate it?)
25
+ User.objects.first()
udata/models/__init__.py CHANGED
@@ -1,6 +1,3 @@
1
- from mongoengine.errors import ValidationError # noqa
2
-
3
- from udata import entrypoints # noqa
4
1
  from udata.mongo import * # noqa
5
2
 
6
3
  # Load all core models and mixins
@@ -26,11 +23,6 @@ from udata.core.dataservices.models import * # noqa
26
23
  from udata.core.pages.models import * # noqa
27
24
 
28
25
  from udata.features.transfer.models import * # noqa
29
- from udata.features.territories.models import * # noqa
30
26
 
31
27
  # Load HarvestSource model as harvest for catalog
32
28
  from udata.harvest.models import HarvestSource as Harvest # noqa
33
-
34
-
35
- def init_app(app):
36
- entrypoints.get_enabled("udata.models", app)
udata/routing.py CHANGED
@@ -11,7 +11,6 @@ from udata import models
11
11
  from udata.core.dataservices.models import Dataservice
12
12
  from udata.core.spatial.models import GeoZone
13
13
  from udata.harvest.models import HarvestSource
14
- from udata.i18n import ISO_639_1_CODES
15
14
  from udata.mongo import db
16
15
  from udata.uris import cdata_url, homepage_url
17
16
 
@@ -23,12 +22,6 @@ class LazyRedirect(object):
23
22
  self.arg = arg
24
23
 
25
24
 
26
- class LanguagePrefixConverter(BaseConverter):
27
- def __init__(self, map):
28
- super(LanguagePrefixConverter, self).__init__(map)
29
- self.regex = "(?:%s)" % "|".join(ISO_639_1_CODES)
30
-
31
-
32
25
  class ListConverter(BaseConverter):
33
26
  def to_python(self, value):
34
27
  return value.split(",")
@@ -241,7 +234,6 @@ def lazy_raise_or_redirect():
241
234
 
242
235
  def init_app(app):
243
236
  app.before_request(lazy_raise_or_redirect)
244
- app.url_map.converters["lang"] = LanguagePrefixConverter
245
237
  app.url_map.converters["list"] = ListConverter
246
238
  app.url_map.converters["pathlist"] = PathListConverter
247
239
  app.url_map.converters["uuid"] = UUIDConverter