udata 13.0.1.dev12__py3-none-any.whl → 14.4.1.dev7__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 (177) hide show
  1. udata/api/__init__.py +2 -8
  2. udata/api_fields.py +35 -4
  3. udata/app.py +30 -50
  4. udata/auth/__init__.py +29 -6
  5. udata/auth/forms.py +8 -6
  6. udata/auth/views.py +6 -3
  7. udata/commands/__init__.py +2 -14
  8. udata/commands/db.py +13 -25
  9. udata/commands/info.py +0 -16
  10. udata/commands/serve.py +3 -11
  11. udata/commands/tests/test_fixtures.py +9 -9
  12. udata/core/access_type/api.py +1 -1
  13. udata/core/access_type/constants.py +12 -8
  14. udata/core/activity/api.py +5 -6
  15. udata/core/avatars/api.py +43 -0
  16. udata/core/avatars/test_avatar_api.py +30 -0
  17. udata/core/badges/tests/test_commands.py +6 -6
  18. udata/core/csv.py +5 -0
  19. udata/core/dataservices/models.py +15 -3
  20. udata/core/dataservices/tasks.py +7 -0
  21. udata/core/dataset/api.py +2 -0
  22. udata/core/dataset/models.py +2 -2
  23. udata/core/dataset/permissions.py +31 -0
  24. udata/core/dataset/tasks.py +50 -10
  25. udata/core/discussions/models.py +1 -0
  26. udata/core/metrics/__init__.py +0 -6
  27. udata/core/organization/api.py +8 -5
  28. udata/core/organization/mails.py +1 -1
  29. udata/core/organization/models.py +9 -1
  30. udata/core/organization/notifications.py +84 -0
  31. udata/core/organization/permissions.py +1 -1
  32. udata/core/organization/tasks.py +3 -0
  33. udata/core/pages/tests/test_api.py +32 -0
  34. udata/core/post/api.py +24 -69
  35. udata/core/post/models.py +84 -16
  36. udata/core/post/tests/test_api.py +24 -1
  37. udata/core/reports/api.py +18 -0
  38. udata/core/reports/models.py +42 -2
  39. udata/core/reuse/models.py +1 -1
  40. udata/core/reuse/tasks.py +7 -0
  41. udata/core/site/models.py +2 -6
  42. udata/core/spatial/commands.py +2 -4
  43. udata/core/spatial/forms.py +2 -2
  44. udata/core/spatial/models.py +0 -10
  45. udata/core/spatial/tests/test_api.py +1 -36
  46. udata/core/user/models.py +15 -2
  47. udata/cors.py +2 -5
  48. udata/db/migrations.py +279 -0
  49. udata/features/notifications/api.py +7 -18
  50. udata/features/notifications/models.py +56 -0
  51. udata/features/notifications/tasks.py +25 -0
  52. udata/flask_mongoengine/engine.py +0 -4
  53. udata/frontend/__init__.py +3 -122
  54. udata/frontend/markdown.py +2 -1
  55. udata/harvest/actions.py +24 -9
  56. udata/harvest/api.py +30 -22
  57. udata/harvest/backends/__init__.py +21 -9
  58. udata/harvest/backends/base.py +29 -3
  59. udata/harvest/backends/ckan/harvesters.py +13 -2
  60. udata/harvest/backends/dcat.py +3 -0
  61. udata/harvest/backends/maaf.py +1 -0
  62. udata/harvest/commands.py +39 -4
  63. udata/harvest/filters.py +17 -6
  64. udata/harvest/forms.py +9 -6
  65. udata/harvest/models.py +16 -0
  66. udata/harvest/permissions.py +27 -0
  67. udata/harvest/tasks.py +3 -5
  68. udata/harvest/tests/ckan/test_ckan_backend.py +35 -2
  69. udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
  70. udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
  71. udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
  72. udata/harvest/tests/dcat/udata.xml +6 -6
  73. udata/harvest/tests/factories.py +1 -1
  74. udata/harvest/tests/test_actions.py +63 -8
  75. udata/harvest/tests/test_api.py +278 -123
  76. udata/harvest/tests/test_base_backend.py +88 -1
  77. udata/harvest/tests/test_dcat_backend.py +60 -13
  78. udata/harvest/tests/test_filters.py +6 -0
  79. udata/i18n.py +11 -273
  80. udata/mail.py +5 -1
  81. udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
  82. udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
  83. udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
  84. udata/models/__init__.py +0 -8
  85. udata/mongo/slug_fields.py +1 -1
  86. udata/rdf.py +45 -6
  87. udata/routing.py +2 -10
  88. udata/sentry.py +4 -10
  89. udata/settings.py +23 -17
  90. udata/tasks.py +4 -3
  91. udata/templates/mail/message.html +5 -31
  92. udata/tests/__init__.py +28 -12
  93. udata/tests/api/__init__.py +108 -21
  94. udata/tests/api/test_activities_api.py +36 -0
  95. udata/tests/api/test_auth_api.py +121 -95
  96. udata/tests/api/test_base_api.py +7 -4
  97. udata/tests/api/test_dataservices_api.py +29 -1
  98. udata/tests/api/test_datasets_api.py +45 -21
  99. udata/tests/api/test_organizations_api.py +192 -197
  100. udata/tests/api/test_reports_api.py +157 -0
  101. udata/tests/api/test_reuses_api.py +147 -147
  102. udata/tests/api/test_security_api.py +12 -12
  103. udata/tests/api/test_swagger.py +4 -4
  104. udata/tests/api/test_tags_api.py +8 -8
  105. udata/tests/api/test_user_api.py +13 -1
  106. udata/tests/apiv2/test_swagger.py +4 -4
  107. udata/tests/apiv2/test_topics.py +1 -1
  108. udata/tests/cli/test_cli_base.py +8 -9
  109. udata/tests/dataset/test_dataset_commands.py +4 -4
  110. udata/tests/dataset/test_dataset_model.py +66 -26
  111. udata/tests/dataset/test_dataset_rdf.py +99 -5
  112. udata/tests/dataset/test_resource_preview.py +0 -1
  113. udata/tests/frontend/test_auth.py +24 -1
  114. udata/tests/frontend/test_csv.py +0 -3
  115. udata/tests/helpers.py +37 -27
  116. udata/tests/organization/test_notifications.py +67 -2
  117. udata/tests/plugin.py +6 -261
  118. udata/tests/site/test_site_csv_exports.py +22 -10
  119. udata/tests/test_activity.py +9 -9
  120. udata/tests/test_cors.py +1 -1
  121. udata/tests/test_dcat_commands.py +2 -2
  122. udata/tests/test_discussions.py +5 -5
  123. udata/tests/test_migrations.py +181 -481
  124. udata/tests/test_notifications.py +15 -57
  125. udata/tests/test_notifications_task.py +43 -0
  126. udata/tests/test_owned.py +81 -1
  127. udata/tests/test_storages.py +25 -19
  128. udata/tests/test_topics.py +77 -61
  129. udata/tests/test_uris.py +33 -0
  130. udata/tests/workers/test_jobs_commands.py +23 -23
  131. udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
  132. udata/translations/ar/LC_MESSAGES/udata.po +187 -108
  133. udata/translations/de/LC_MESSAGES/udata.mo +0 -0
  134. udata/translations/de/LC_MESSAGES/udata.po +187 -108
  135. udata/translations/es/LC_MESSAGES/udata.mo +0 -0
  136. udata/translations/es/LC_MESSAGES/udata.po +187 -108
  137. udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
  138. udata/translations/fr/LC_MESSAGES/udata.po +188 -109
  139. udata/translations/it/LC_MESSAGES/udata.mo +0 -0
  140. udata/translations/it/LC_MESSAGES/udata.po +187 -108
  141. udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
  142. udata/translations/pt/LC_MESSAGES/udata.po +187 -108
  143. udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
  144. udata/translations/sr/LC_MESSAGES/udata.po +187 -108
  145. udata/translations/udata.pot +215 -106
  146. udata/uris.py +0 -2
  147. udata/utils.py +5 -0
  148. udata-14.4.1.dev7.dist-info/METADATA +109 -0
  149. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +153 -166
  150. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +3 -5
  151. udata/core/followers/views.py +0 -15
  152. udata/core/post/forms.py +0 -30
  153. udata/entrypoints.py +0 -93
  154. udata/features/identicon/__init__.py +0 -0
  155. udata/features/identicon/api.py +0 -13
  156. udata/features/identicon/backends.py +0 -131
  157. udata/features/identicon/tests/__init__.py +0 -0
  158. udata/features/identicon/tests/test_backends.py +0 -18
  159. udata/features/territories/__init__.py +0 -49
  160. udata/features/territories/api.py +0 -25
  161. udata/features/territories/models.py +0 -51
  162. udata/flask_mongoengine/json.py +0 -38
  163. udata/migrations/__init__.py +0 -367
  164. udata/templates/mail/base.html +0 -105
  165. udata/templates/mail/base.txt +0 -6
  166. udata/templates/mail/button.html +0 -3
  167. udata/templates/mail/layouts/1-column.html +0 -19
  168. udata/templates/mail/layouts/2-columns.html +0 -20
  169. udata/templates/mail/layouts/center-panel.html +0 -16
  170. udata/tests/cli/test_db_cli.py +0 -68
  171. udata/tests/features/territories/__init__.py +0 -20
  172. udata/tests/features/territories/test_territories_api.py +0 -185
  173. udata/tests/frontend/test_hooks.py +0 -149
  174. udata-13.0.1.dev12.dist-info/METADATA +0 -133
  175. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/WHEEL +0 -0
  176. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
  177. {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/top_level.txt +0 -0
@@ -1,185 +0,0 @@
1
- import pytest
2
- from flask import url_for
3
-
4
- from udata.core.spatial.factories import GeoZoneFactory
5
- from udata.tests.api import APITestCase
6
- from udata.tests.features.territories import (
7
- create_geozones_fixtures,
8
- create_old_new_regions_fixtures,
9
- )
10
-
11
-
12
- @pytest.mark.options(
13
- ACTIVATE_TERRITORIES=True,
14
- HANDLED_LEVELS=("fr:commune", "fr:departement", "fr:region", "country"),
15
- )
16
- class TerritoriesAPITest(APITestCase):
17
- def setUp(self):
18
- self.paca, self.bdr, self.arles = create_geozones_fixtures()
19
-
20
- def test_suggest_no_parameter(self):
21
- response = self.get(url_for("api.suggest_territory"))
22
- self.assert400(response)
23
-
24
- def test_suggest_empty(self):
25
- response = self.get(url_for("api.suggest_territory", q="tes"))
26
- self.assert200(response)
27
- self.assertEqual(response.json, [])
28
- response = self.get(url_for("api.suggest_territory", q="test"))
29
- self.assert200(response)
30
- self.assertEqual(response.json, [])
31
-
32
- def test_suggest_town(self):
33
- response = self.get(url_for("api.suggest_territory", q="arle"))
34
- self.assert200(response)
35
- result = response.json[0]
36
- self.assertEqual(result["title"], self.arles.name)
37
- self.assertEqual(result["id"], self.arles.id)
38
- self.assertIn("page", result)
39
-
40
- def test_suggest_town_five_letters(self):
41
- response = self.get(url_for("api.suggest_territory", q="arles"))
42
- self.assert200(response)
43
- result = response.json[0]
44
- self.assertEqual(result["title"], self.arles.name)
45
- self.assertEqual(result["id"], self.arles.id)
46
- self.assertIn("page", result)
47
-
48
- def test_suggest_town_by_insee_code(self):
49
- response = self.get(url_for("api.suggest_territory", q="13004"))
50
- self.assert200(response)
51
- result = response.json[0]
52
- self.assertEqual(result["id"], self.arles.id)
53
- self.assertEqual(result["title"], self.arles.name)
54
-
55
- def test_suggest_towns(self):
56
- arles_sur_tech = GeoZoneFactory(
57
- id="fr:commune:66009", level="fr:commune", name="Arles-sur-Tech", code="66009"
58
- )
59
- response = self.get(url_for("api.suggest_territory", q="arles"))
60
- self.assert200(response)
61
- results = response.json
62
- self.assertEqual(len(results), 2)
63
- # Arles must be first given the population.
64
- self.assertEqual(results[0]["id"], self.arles.id)
65
- self.assertEqual(results[1]["id"], arles_sur_tech.id)
66
-
67
- def test_suggest_county(self):
68
- response = self.get(url_for("api.suggest_territory", q="bouche"))
69
- self.assert200(response)
70
- result = response.json[0]
71
- self.assertEqual(result["title"], self.bdr.name)
72
- self.assertEqual(result["id"], self.bdr.id)
73
- self.assertIn("page", result)
74
-
75
- def test_suggest_region(self):
76
- response = self.get(url_for("api.suggest_territory", q="prov"))
77
- self.assert200(response)
78
- result = response.json[0]
79
- self.assertEqual(result["title"], self.paca.name)
80
- self.assertEqual(result["id"], self.paca.id)
81
- self.assertIn("page", result)
82
-
83
- def test_suggest_old_new_region(self):
84
- lr, occitanie = create_old_new_regions_fixtures()
85
- response = self.get(url_for("api.suggest_territory", q="langue"))
86
- self.assert200(response)
87
- self.assertEqual(len(response.json), 2)
88
- result = response.json[0]
89
- self.assertEqual(result["title"], occitanie.name)
90
- self.assertEqual(result["id"], occitanie.id)
91
- result = response.json[1]
92
- self.assertEqual(result["title"], lr.name)
93
- self.assertEqual(result["id"], lr.id)
94
-
95
- def test_suggest_county_by_id(self):
96
- response = self.get(url_for("api.suggest_territory", q="13"))
97
- self.assert200(response)
98
- result = response.json[0]
99
- self.assertEqual(result["id"], self.bdr.id)
100
- self.assertEqual(result["title"], self.bdr.name)
101
-
102
- def test_suggest_town_and_county(self):
103
- bouchet = GeoZoneFactory(
104
- id="fr:commune:26054", level="fr:commune", name="Bouchet", code="26054"
105
- )
106
- response = self.get(url_for("api.suggest_territory", q="bouche"))
107
- self.assert200(response)
108
- results = response.json
109
- self.assertEqual(len(results), 2)
110
-
111
- self.assertEqual(results[0]["id"], bouchet.id)
112
- self.assertEqual(results[1]["id"], self.bdr.id)
113
-
114
- def test_suggest_drom_com(self):
115
- guyane = GeoZoneFactory(
116
- id="fr:departement:973", level="fr:departement", name="Guyane", code="973"
117
- )
118
- response = self.get(url_for("api.suggest_territory", q="guya"))
119
- self.assert200(response)
120
- results = response.json
121
- self.assertEqual(len(results), 1)
122
- self.assertEqual(results[0]["id"], guyane.id)
123
-
124
- def test_suggest_drom_com_by_code(self):
125
- guyane = GeoZoneFactory(
126
- id="fr:departement:973", level="fr:departement", name="Guyane", code="973"
127
- )
128
- response = self.get(url_for("api.suggest_territory", q="973"))
129
- self.assert200(response)
130
- results = response.json
131
- self.assertEqual(len(results), 1)
132
- self.assertEqual(results[0]["id"], guyane.id)
133
-
134
- def test_suggest_corsica(self):
135
- bastia = GeoZoneFactory(
136
- id="fr:commune:2b033", level="fr:commune", name="Bastia", code="2b033"
137
- )
138
- response = self.get(url_for("api.suggest_territory", q="basti"))
139
- self.assert200(response)
140
- results = response.json
141
- self.assertEqual(len(results), 1)
142
- self.assertEqual(results[0]["id"], bastia.id)
143
-
144
- def test_suggest_corsica_by_code(self):
145
- bastia = GeoZoneFactory(
146
- id="fr:commune:2b033", level="fr:commune", name="Bastia", code="2b033"
147
- )
148
- response = self.get(url_for("api.suggest_territory", q="2b033"))
149
- self.assert200(response)
150
- results = response.json
151
- self.assertEqual(len(results), 1)
152
- self.assertEqual(results[0]["id"], bastia.id)
153
-
154
- def test_suggest_corsica_by_county_code(self):
155
- haute_corse = GeoZoneFactory(
156
- id="fr:departement:2b", level="fr:departement", name="Haute-Corse", code="2b"
157
- )
158
- response = self.get(url_for("api.suggest_territory", q="2b"))
159
- self.assert200(response)
160
- results = response.json
161
- self.assertEqual(len(results), 1)
162
- self.assertEqual(results[0]["id"], haute_corse.id)
163
-
164
- def test_not_suggest_country(self):
165
- GeoZoneFactory(id="country:fr", level="country", name="France")
166
- response = self.get(url_for("api.suggest_territory", q="fra"))
167
- self.assert200(response)
168
- results = response.json
169
- self.assertEqual(len(results), 0)
170
- response = self.get(url_for("api.suggest_territory", q="fran"))
171
- self.assert200(response)
172
- results = response.json
173
- self.assertEqual(len(results), 0)
174
- response = self.get(url_for("api.suggest_territory", q="franc"))
175
- self.assert200(response)
176
- results = response.json
177
- self.assertEqual(len(results), 0)
178
-
179
- def test_suggest_unicode(self):
180
- response = self.get(url_for("api.suggest_territory", q="Bouches-du-Rhône"))
181
- self.assert200(response)
182
- result = response.json[0]
183
- self.assertEqual(result["title"], self.bdr.name)
184
- self.assertEqual(result["id"], self.bdr.id)
185
- self.assertIn("page", result)
@@ -1,149 +0,0 @@
1
- import pytest
2
- from flask import Blueprint, render_template_string, url_for
3
-
4
- from udata.frontend import template_hook
5
- from udata.tests.api import PytestOnlyAPITestCase
6
- from udata.tests.helpers import assert200
7
-
8
- bp = Blueprint("hooks_tests", __name__, url_prefix="/hooks_tests")
9
-
10
-
11
- @template_hook
12
- def single(ctx):
13
- return "single"
14
-
15
-
16
- @template_hook
17
- def hello(ctx, name):
18
- return "Hello {}".format(name)
19
-
20
-
21
- @template_hook
22
- def kwargs(ctx, **kwargs):
23
- return ", ".join(sorted("{0}={1}".format(k, v) for k, v in kwargs.items()))
24
-
25
-
26
- @template_hook("multiple")
27
- def first(ctx):
28
- return "first"
29
-
30
-
31
- @template_hook("multiple")
32
- def second(ctx):
33
- return "second"
34
-
35
-
36
- @template_hook("conditionnal", when=lambda ctx: True)
37
- def true(ctx):
38
- return "true"
39
-
40
-
41
- @template_hook("conditionnal", when=lambda ctx: False)
42
- def false(ctx):
43
- return "false"
44
-
45
-
46
- @bp.route("/empty/render")
47
- def render_empty():
48
- return render_template_string('{{ hook("empty") }}')
49
-
50
-
51
- @bp.route("/empty/iter")
52
- def iter_empty():
53
- return render_template_string('{% for w in hook("empty") %}<{{ w }}>{% endfor %}')
54
-
55
-
56
- @bp.route("/hello")
57
- def render_hello():
58
- return render_template_string('{{ hook("hello", "world") }}')
59
-
60
-
61
- @bp.route("/kwargs")
62
- def render_kwargs():
63
- return render_template_string('{{ hook("kwargs", key="value", other=42) }}')
64
-
65
-
66
- @bp.route("/single/render")
67
- def render_single():
68
- return render_template_string('{{ hook("single") }}')
69
-
70
-
71
- @bp.route("/single/iter")
72
- def iter_single():
73
- return render_template_string('{% for w in hook("single") %}<{{ w }}>{% endfor %}')
74
-
75
-
76
- @bp.route("/multiple/render")
77
- def render_multiple():
78
- return render_template_string('{{ hook("multiple") }}')
79
-
80
-
81
- @bp.route("/multiple/iter")
82
- def iter_multiple():
83
- return render_template_string('{% for w in hook("multiple") %}<{{ w }}>{% endfor %}')
84
-
85
-
86
- @bp.route("/conditionnal/render")
87
- def render_conditionnal():
88
- return render_template_string('{{ hook("conditionnal") }}')
89
-
90
-
91
- @bp.route("/conditionnal/iter")
92
- def iter_conditionnal():
93
- return render_template_string('{% for w in hook("conditionnal") %}<{{ w }}>{% endfor %}')
94
-
95
-
96
- class HooksTest(PytestOnlyAPITestCase):
97
- @pytest.fixture(autouse=True)
98
- def setup_func(self, app):
99
- app.register_blueprint(bp)
100
-
101
- def test_empty_template_hook(self, client):
102
- response = client.get(url_for("hooks_tests.render_empty"))
103
- assert200(response)
104
- assert b"" == response.data
105
-
106
- def test_iter_empty_template_hook(self, client):
107
- response = client.get(url_for("hooks_tests.iter_empty"))
108
- assert200(response)
109
- assert b"" == response.data
110
-
111
- def test_single_template_hook(self, client):
112
- response = client.get(url_for("hooks_tests.render_single"))
113
- assert200(response)
114
- assert b"single" == response.data
115
-
116
- def test_iter_single_template_hook(self, client):
117
- response = client.get(url_for("hooks_tests.iter_single"))
118
- assert200(response)
119
- assert b"<single>" == response.data
120
-
121
- def test_multiple_template_hooks(self, client):
122
- response = client.get(url_for("hooks_tests.render_multiple"))
123
- assert200(response)
124
- assert b"firstsecond" == response.data
125
-
126
- def test_iter_multiple_template_hooks(self, client):
127
- response = client.get(url_for("hooks_tests.iter_multiple"))
128
- assert200(response)
129
- assert b"<first><second>" == response.data
130
-
131
- def test_conditionnal_template_hooks(self, client):
132
- response = client.get(url_for("hooks_tests.render_conditionnal"))
133
- assert200(response)
134
- assert b"true" == response.data
135
-
136
- def test_iter_conditionnal_template_hooks(self, client):
137
- response = client.get(url_for("hooks_tests.iter_conditionnal"))
138
- assert200(response)
139
- assert b"<true>" == response.data
140
-
141
- def test_arguments(self, client):
142
- response = client.get(url_for("hooks_tests.render_hello"))
143
- assert200(response)
144
- assert b"Hello world" == response.data
145
-
146
- def test_kwargs(self, client):
147
- response = client.get(url_for("hooks_tests.render_kwargs"))
148
- assert200(response)
149
- assert b"key=value, other=42" == response.data
@@ -1,133 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: udata
3
- Version: 13.0.1.dev12
4
- Summary: Open data portal
5
- Author-email: Opendata Team <opendatateam@data.gouv.fr>
6
- Maintainer-email: Opendata Team <opendatateam@data.gouv.fr>
7
- License: MIT
8
- Project-URL: Homepage, https://github.com/opendatateam/udata
9
- Project-URL: Repository, https://github.com/opendatateam/udata
10
- Project-URL: Documentation, https://udata.readthedocs.io/
11
- Project-URL: Bug Tracker, https://github.com/opendatateam/udata/issues
12
- Keywords: udata,open data,portal,data
13
- Classifier: Development Status :: 5 - Production/Stable
14
- Classifier: Programming Language :: Python
15
- Classifier: Environment :: Web Environment
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Intended Audience :: Developers
18
- Classifier: Topic :: System :: Software Distribution
19
- Classifier: Programming Language :: Python :: 3
20
- Classifier: Programming Language :: Python :: 3.11
21
- Classifier: Programming Language :: Python :: 3.12
22
- Classifier: Programming Language :: Python :: 3.13
23
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
25
- Requires-Python: <3.14,>=3.11
26
- Description-Content-Type: text/markdown
27
- License-File: LICENSE
28
- Requires-Dist: authlib==1.5.1
29
- Requires-Dist: awesome-slugify==1.6.5
30
- Requires-Dist: babel==2.17.0
31
- Requires-Dist: bcrypt==4.3.0
32
- Requires-Dist: bleach[css]==6.2.0
33
- Requires-Dist: blinker==1.5
34
- Requires-Dist: boto3==1.26.102
35
- Requires-Dist: botocore==1.29.165
36
- Requires-Dist: celery==5.4.0
37
- Requires-Dist: celerybeat-mongo==0.2.0
38
- Requires-Dist: click==8.1.8
39
- Requires-Dist: cryptography==44.0.2
40
- Requires-Dist: email-validator==2.2.0
41
- Requires-Dist: factory-boy==3.3.3
42
- Requires-Dist: feedgenerator==2.1.0
43
- Requires-Dist: flask==2.1.2
44
- Requires-Dist: flask-babel==4.0.0
45
- Requires-Dist: flask-caching==2.3.1
46
- Requires-Dist: flask-cdn==1.5.3
47
- Requires-Dist: flask-gravatar==0.5.0
48
- Requires-Dist: flask-login==0.6.3
49
- Requires-Dist: flask-mail==0.10.0
50
- Requires-Dist: flask-principal==0.4.0
51
- Requires-Dist: flask-restx==1.3.0
52
- Requires-Dist: flask-security-too==5.1.2
53
- Requires-Dist: flask-sitemap==0.4.0
54
- Requires-Dist: flask-storage~=1.4.0
55
- Requires-Dist: flask-wtf==1.2.2
56
- Requires-Dist: geojson==3.2.0
57
- Requires-Dist: geomet==1.1.0
58
- Requires-Dist: html2text==2024.2.26
59
- Requires-Dist: humanfriendly==10.0
60
- Requires-Dist: importlib-resources==6.5.2
61
- Requires-Dist: itsdangerous==2.2.0
62
- Requires-Dist: jinja2==3.1.6
63
- Requires-Dist: jsonschema==4.23.0
64
- Requires-Dist: kombu[redis]==5.5.0
65
- Requires-Dist: langdetect==1.0.9
66
- Requires-Dist: levenshtein==0.27.1
67
- Requires-Dist: lxml==5.3.1
68
- Requires-Dist: markupsafe==2.1.2
69
- Requires-Dist: mistune==3.1.3
70
- Requires-Dist: mongoengine==0.29.1
71
- Requires-Dist: netaddr==1.3.0
72
- Requires-Dist: pillow==9.2.0
73
- Requires-Dist: pydenticon==0.3.1
74
- Requires-Dist: pymongo==4.11.3
75
- Requires-Dist: python-dateutil==2.9.0.post0
76
- Requires-Dist: pytz==2025.1
77
- Requires-Dist: rdflib==7.1.3
78
- Requires-Dist: redis==5.2.1
79
- Requires-Dist: requests==2.32.4
80
- Requires-Dist: saxonche==12.8.0
81
- Requires-Dist: sentry-sdk[flask]==2.23.1
82
- Requires-Dist: speaklater==1.3
83
- Requires-Dist: tlds==2025022800
84
- Requires-Dist: typing-extensions==4.12.2
85
- Requires-Dist: tzdata==2025.1
86
- Requires-Dist: urlextract==1.9.0
87
- Requires-Dist: urllib3==1.26.20
88
- Requires-Dist: voluptuous==0.15.2
89
- Requires-Dist: werkzeug==2.2.3
90
- Requires-Dist: wtforms[email]==3.2.1
91
- Requires-Dist: wtforms-json==0.3.5
92
- Provides-Extra: dev
93
- Requires-Dist: flask-shell-ipython<1.0,>=0.5.3; extra == "dev"
94
- Requires-Dist: invoke<3.0,>=2.2.0; extra == "dev"
95
- Requires-Dist: pip-tools<8.0,>=7.4.1; extra == "dev"
96
- Requires-Dist: pre-commit<5.0,>=4.2.0; extra == "dev"
97
- Requires-Dist: ruff<1.0,>=0.11.0; extra == "dev"
98
- Provides-Extra: test
99
- Requires-Dist: faker<38.0,>=37.0.2; extra == "test"
100
- Requires-Dist: feedparser<7.0,>=6.0.11; extra == "test"
101
- Requires-Dist: httpretty<2.0,>=1.1.4; extra == "test"
102
- Requires-Dist: mock<6.0,>=5.2.0; extra == "test"
103
- Requires-Dist: pytest<9.0,>=8.3.5; extra == "test"
104
- Requires-Dist: pytest-env<2.0,>=1.1.5; extra == "test"
105
- Requires-Dist: pytest-flask<2.0,>=1.3.0; extra == "test"
106
- Requires-Dist: pytest-mock<4.0,>=3.14.0; extra == "test"
107
- Requires-Dist: pytest-sugar<2.0,>=1.0.0; extra == "test"
108
- Requires-Dist: requests-mock<2.0,>=1.12.1; extra == "test"
109
- Provides-Extra: doc
110
- Requires-Dist: mkdocs<2.0,>=1.6.1; extra == "doc"
111
- Provides-Extra: report
112
- Requires-Dist: coverage[toml]<8.0,>=7.7.0; extra == "report"
113
- Requires-Dist: flake8<8.0,>=7.1.2; extra == "report"
114
- Requires-Dist: pytest-cov<8.0,>=7.0.0; extra == "report"
115
- Dynamic: license-file
116
-
117
- <p align="center"><img src="https://i.imgur.com/rlRox1c.png"></p>
118
-
119
- udata
120
- =====
121
-
122
- Customizable and skinnable social platform dedicated to (open) data.
123
-
124
- The [full documentation][readthedocs-url] is hosted on Read the Docs.
125
-
126
- udata is maintained by [data.gouv.fr](https://data.gouv.fr/), the
127
- French public agency in charge of open data. data.gouv.fr is responsible
128
- for publishing udata's roadmap and for building consensus around it.
129
-
130
- It is collectively taken care of by members of the
131
- [OpenDataTeam](https://github.com/opendatateam).
132
-
133
- [readthedocs-url]: https://udata.readthedocs.io/en/stable/