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.
- udata/api/__init__.py +2 -8
- udata/api_fields.py +35 -4
- udata/app.py +30 -50
- udata/auth/__init__.py +29 -6
- udata/auth/forms.py +8 -6
- udata/auth/views.py +6 -3
- udata/commands/__init__.py +2 -14
- udata/commands/db.py +13 -25
- udata/commands/info.py +0 -16
- udata/commands/serve.py +3 -11
- udata/commands/tests/test_fixtures.py +9 -9
- udata/core/access_type/api.py +1 -1
- udata/core/access_type/constants.py +12 -8
- udata/core/activity/api.py +5 -6
- udata/core/avatars/api.py +43 -0
- udata/core/avatars/test_avatar_api.py +30 -0
- udata/core/badges/tests/test_commands.py +6 -6
- udata/core/csv.py +5 -0
- udata/core/dataservices/models.py +15 -3
- udata/core/dataservices/tasks.py +7 -0
- udata/core/dataset/api.py +2 -0
- udata/core/dataset/models.py +2 -2
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/tasks.py +50 -10
- udata/core/discussions/models.py +1 -0
- udata/core/metrics/__init__.py +0 -6
- udata/core/organization/api.py +8 -5
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +9 -1
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/tests/test_api.py +32 -0
- udata/core/post/api.py +24 -69
- udata/core/post/models.py +84 -16
- udata/core/post/tests/test_api.py +24 -1
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/site/models.py +2 -6
- udata/core/spatial/commands.py +2 -4
- udata/core/spatial/forms.py +2 -2
- udata/core/spatial/models.py +0 -10
- udata/core/spatial/tests/test_api.py +1 -36
- udata/core/user/models.py +15 -2
- udata/cors.py +2 -5
- udata/db/migrations.py +279 -0
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +56 -0
- udata/features/notifications/tasks.py +25 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/frontend/__init__.py +3 -122
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +24 -9
- udata/harvest/api.py +30 -22
- udata/harvest/backends/__init__.py +21 -9
- udata/harvest/backends/base.py +29 -3
- udata/harvest/backends/ckan/harvesters.py +13 -2
- udata/harvest/backends/dcat.py +3 -0
- udata/harvest/backends/maaf.py +1 -0
- udata/harvest/commands.py +39 -4
- udata/harvest/filters.py +17 -6
- udata/harvest/forms.py +9 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tasks.py +3 -5
- udata/harvest/tests/ckan/test_ckan_backend.py +35 -2
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
- udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
- udata/harvest/tests/dcat/udata.xml +6 -6
- udata/harvest/tests/factories.py +1 -1
- udata/harvest/tests/test_actions.py +63 -8
- udata/harvest/tests/test_api.py +278 -123
- udata/harvest/tests/test_base_backend.py +88 -1
- udata/harvest/tests/test_dcat_backend.py +60 -13
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +11 -273
- udata/mail.py +5 -1
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/models/__init__.py +0 -8
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +45 -6
- udata/routing.py +2 -10
- udata/sentry.py +4 -10
- udata/settings.py +23 -17
- udata/tasks.py +4 -3
- udata/templates/mail/message.html +5 -31
- udata/tests/__init__.py +28 -12
- udata/tests/api/__init__.py +108 -21
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_auth_api.py +121 -95
- udata/tests/api/test_base_api.py +7 -4
- udata/tests/api/test_dataservices_api.py +29 -1
- udata/tests/api/test_datasets_api.py +45 -21
- udata/tests/api/test_organizations_api.py +192 -197
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_reuses_api.py +147 -147
- udata/tests/api/test_security_api.py +12 -12
- udata/tests/api/test_swagger.py +4 -4
- udata/tests/api/test_tags_api.py +8 -8
- udata/tests/api/test_user_api.py +13 -1
- udata/tests/apiv2/test_swagger.py +4 -4
- udata/tests/apiv2/test_topics.py +1 -1
- udata/tests/cli/test_cli_base.py +8 -9
- udata/tests/dataset/test_dataset_commands.py +4 -4
- udata/tests/dataset/test_dataset_model.py +66 -26
- udata/tests/dataset/test_dataset_rdf.py +99 -5
- udata/tests/dataset/test_resource_preview.py +0 -1
- udata/tests/frontend/test_auth.py +24 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +37 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_cors.py +1 -1
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_migrations.py +181 -481
- udata/tests/test_notifications.py +15 -57
- udata/tests/test_notifications_task.py +43 -0
- udata/tests/test_owned.py +81 -1
- udata/tests/test_storages.py +25 -19
- udata/tests/test_topics.py +77 -61
- udata/tests/test_uris.py +33 -0
- udata/tests/workers/test_jobs_commands.py +23 -23
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +187 -108
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +187 -108
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +187 -108
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +188 -109
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +187 -108
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +187 -108
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +187 -108
- udata/translations/udata.pot +215 -106
- udata/uris.py +0 -2
- udata/utils.py +5 -0
- udata-14.4.1.dev7.dist-info/METADATA +109 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +153 -166
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +3 -5
- udata/core/followers/views.py +0 -15
- udata/core/post/forms.py +0 -30
- udata/entrypoints.py +0 -93
- udata/features/identicon/__init__.py +0 -0
- udata/features/identicon/api.py +0 -13
- udata/features/identicon/backends.py +0 -131
- udata/features/identicon/tests/__init__.py +0 -0
- udata/features/identicon/tests/test_backends.py +0 -18
- udata/features/territories/__init__.py +0 -49
- udata/features/territories/api.py +0 -25
- udata/features/territories/models.py +0 -51
- udata/flask_mongoengine/json.py +0 -38
- udata/migrations/__init__.py +0 -367
- udata/templates/mail/base.html +0 -105
- udata/templates/mail/base.txt +0 -6
- udata/templates/mail/button.html +0 -3
- udata/templates/mail/layouts/1-column.html +0 -19
- udata/templates/mail/layouts/2-columns.html +0 -20
- udata/templates/mail/layouts/center-panel.html +0 -16
- udata/tests/cli/test_db_cli.py +0 -68
- udata/tests/features/territories/__init__.py +0 -20
- udata/tests/features/territories/test_territories_api.py +0 -185
- udata/tests/frontend/test_hooks.py +0 -149
- udata-13.0.1.dev12.dist-info/METADATA +0 -133
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/WHEEL +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/top_level.txt +0 -0
udata/migrations/__init__.py
DELETED
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Data migrations logic
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import importlib.util
|
|
6
|
-
import inspect
|
|
7
|
-
import logging
|
|
8
|
-
import os
|
|
9
|
-
import queue
|
|
10
|
-
import traceback
|
|
11
|
-
from datetime import datetime
|
|
12
|
-
from logging.handlers import QueueHandler
|
|
13
|
-
|
|
14
|
-
from flask import current_app
|
|
15
|
-
from mongoengine.connection import get_db
|
|
16
|
-
from pkg_resources import (
|
|
17
|
-
resource_filename,
|
|
18
|
-
resource_isdir,
|
|
19
|
-
resource_listdir,
|
|
20
|
-
resource_string,
|
|
21
|
-
)
|
|
22
|
-
from pymongo import ReturnDocument
|
|
23
|
-
|
|
24
|
-
from udata import entrypoints
|
|
25
|
-
|
|
26
|
-
log = logging.getLogger(__name__)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class MigrationError(Exception):
|
|
30
|
-
"""
|
|
31
|
-
Raised on migration execution error.
|
|
32
|
-
|
|
33
|
-
:param msg str: A human readable message (a reason)
|
|
34
|
-
:param output str: An optionnal array of logging output
|
|
35
|
-
:param exc Exception: An optionnal underlying exception
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self, msg, output=None, exc=None, traceback=None):
|
|
39
|
-
super().__init__(msg)
|
|
40
|
-
self.msg = msg
|
|
41
|
-
self.output = output
|
|
42
|
-
self.exc = exc
|
|
43
|
-
self.traceback = traceback
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class RollbackError(MigrationError):
|
|
47
|
-
"""
|
|
48
|
-
Raised on rollback.
|
|
49
|
-
Hold the initial migration error and rollback exception (if any)
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, msg, output=None, exc=None, migrate_exc=None):
|
|
53
|
-
super().__init__(msg)
|
|
54
|
-
self.msg = msg
|
|
55
|
-
self.output = output
|
|
56
|
-
self.exc = exc
|
|
57
|
-
self.migrate_exc = migrate_exc
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
class MigrationFormatter(logging.Formatter):
|
|
61
|
-
pass
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
class Record(dict):
|
|
65
|
-
"""
|
|
66
|
-
A simple wrapper to migrations document
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
__getattr__ = dict.get
|
|
70
|
-
|
|
71
|
-
def load(self):
|
|
72
|
-
specs = {"plugin": self["plugin"], "filename": self["filename"]}
|
|
73
|
-
self.clear()
|
|
74
|
-
data = get_db().migrations.find_one(specs)
|
|
75
|
-
self.update(data or specs)
|
|
76
|
-
|
|
77
|
-
def exists(self):
|
|
78
|
-
return bool(self._id)
|
|
79
|
-
|
|
80
|
-
def __bool__(self):
|
|
81
|
-
return self.exists()
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def collection(self):
|
|
85
|
-
return get_db().migrations
|
|
86
|
-
|
|
87
|
-
@property
|
|
88
|
-
def status(self):
|
|
89
|
-
"""
|
|
90
|
-
Status is the status of the last operation.
|
|
91
|
-
|
|
92
|
-
Will be `None` if the record doesn't exists.
|
|
93
|
-
Possible values are:
|
|
94
|
-
- success
|
|
95
|
-
- rollback
|
|
96
|
-
- rollback-error
|
|
97
|
-
- error
|
|
98
|
-
- recorded
|
|
99
|
-
"""
|
|
100
|
-
if not self.exists():
|
|
101
|
-
return
|
|
102
|
-
op = self.ops[-1]
|
|
103
|
-
if op["success"]:
|
|
104
|
-
if op["type"] == "migrate":
|
|
105
|
-
return "success"
|
|
106
|
-
elif op["type"] == "rollback":
|
|
107
|
-
return "rollback"
|
|
108
|
-
elif op["type"] == "record":
|
|
109
|
-
return "recorded"
|
|
110
|
-
else:
|
|
111
|
-
return "unknown"
|
|
112
|
-
else:
|
|
113
|
-
return "rollback-error" if op["type"] == "rollback" else "error"
|
|
114
|
-
|
|
115
|
-
@property
|
|
116
|
-
def last_date(self):
|
|
117
|
-
if not self.exists():
|
|
118
|
-
return
|
|
119
|
-
op = self.ops[-1]
|
|
120
|
-
return op["date"]
|
|
121
|
-
|
|
122
|
-
@property
|
|
123
|
-
def ok(self):
|
|
124
|
-
"""
|
|
125
|
-
Is true if the migration is considered as successfully applied
|
|
126
|
-
"""
|
|
127
|
-
if not self.exists():
|
|
128
|
-
return False
|
|
129
|
-
op = self.ops[-1]
|
|
130
|
-
return op["success"] and op["type"] in ("migrate", "record")
|
|
131
|
-
|
|
132
|
-
def add(self, _type, migration, output, state, success):
|
|
133
|
-
script = inspect.getsource(migration)
|
|
134
|
-
return Record(
|
|
135
|
-
self.collection.find_one_and_update(
|
|
136
|
-
{"plugin": self.plugin, "filename": self.filename},
|
|
137
|
-
{
|
|
138
|
-
"$push": {
|
|
139
|
-
"ops": {
|
|
140
|
-
"date": datetime.utcnow(),
|
|
141
|
-
"type": _type,
|
|
142
|
-
"script": script,
|
|
143
|
-
"output": output,
|
|
144
|
-
"state": state,
|
|
145
|
-
"success": success,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
upsert=True,
|
|
150
|
-
return_document=ReturnDocument.AFTER,
|
|
151
|
-
)
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
def delete(self):
|
|
155
|
-
return self.collection.delete_one({"_id": self._id})
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
class Migration:
|
|
159
|
-
def __init__(self, plugin_or_specs, filename, module_name=None):
|
|
160
|
-
if filename is None and ":" in plugin_or_specs:
|
|
161
|
-
plugin, filename = plugin_or_specs.split(":")
|
|
162
|
-
else:
|
|
163
|
-
plugin = plugin_or_specs
|
|
164
|
-
if not filename.endswith(".py"):
|
|
165
|
-
filename += ".py"
|
|
166
|
-
|
|
167
|
-
self.plugin = plugin
|
|
168
|
-
self.filename = filename
|
|
169
|
-
self.module_name = module_name
|
|
170
|
-
self._record = None
|
|
171
|
-
self._module = None
|
|
172
|
-
|
|
173
|
-
@property
|
|
174
|
-
def collection(self):
|
|
175
|
-
return get_db().migrations
|
|
176
|
-
|
|
177
|
-
@property
|
|
178
|
-
def db_query(self):
|
|
179
|
-
return {"plugin": self.plugin, "filename": self.filename}
|
|
180
|
-
|
|
181
|
-
@property
|
|
182
|
-
def label(self):
|
|
183
|
-
return ":".join((self.plugin, self.filename))
|
|
184
|
-
|
|
185
|
-
@property
|
|
186
|
-
def record(self):
|
|
187
|
-
if self._record is None:
|
|
188
|
-
specs = {"plugin": self.plugin, "filename": self.filename}
|
|
189
|
-
data = get_db().migrations.find_one(specs)
|
|
190
|
-
self._record = Record(data or specs)
|
|
191
|
-
return self._record
|
|
192
|
-
|
|
193
|
-
@property
|
|
194
|
-
def module(self):
|
|
195
|
-
if self._module is None:
|
|
196
|
-
self._module = load_migration(self.plugin, self.filename, module_name=self.module_name)
|
|
197
|
-
return self._module
|
|
198
|
-
|
|
199
|
-
def __eq__(self, value):
|
|
200
|
-
return (
|
|
201
|
-
isinstance(value, Migration)
|
|
202
|
-
and getattr(value, "plugin") == self.plugin
|
|
203
|
-
and getattr(value, "filename") == self.filename
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
def execute(self, recordonly=False, dryrun=False):
|
|
207
|
-
"""
|
|
208
|
-
Execute a migration
|
|
209
|
-
|
|
210
|
-
If recordonly is True, the migration is only recorded
|
|
211
|
-
If dryrun is True, the migration is neither executed nor recorded
|
|
212
|
-
"""
|
|
213
|
-
q = queue.Queue(-1) # no limit on size
|
|
214
|
-
handler = QueueHandler(q)
|
|
215
|
-
handler.setFormatter(MigrationFormatter())
|
|
216
|
-
logger = getattr(self.module, "log", logging.getLogger(self.module.__name__))
|
|
217
|
-
logger.propagate = False
|
|
218
|
-
for h in logger.handlers:
|
|
219
|
-
logger.removeHandler(h)
|
|
220
|
-
logger.addHandler(handler)
|
|
221
|
-
|
|
222
|
-
if not hasattr(self.module, "migrate"):
|
|
223
|
-
error = SyntaxError("A migration should at least have a migrate(db) function")
|
|
224
|
-
raise MigrationError("Error while executing migration", exc=error)
|
|
225
|
-
|
|
226
|
-
out = [["info", "Recorded only"]] if recordonly else []
|
|
227
|
-
state = {}
|
|
228
|
-
|
|
229
|
-
if not recordonly and not dryrun:
|
|
230
|
-
db = get_db()
|
|
231
|
-
db._state = state
|
|
232
|
-
try:
|
|
233
|
-
self.module.migrate(db)
|
|
234
|
-
out = _extract_output(q)
|
|
235
|
-
except Exception as e:
|
|
236
|
-
out = _extract_output(q)
|
|
237
|
-
tb = traceback.format_exc()
|
|
238
|
-
self.add_record("migrate", out, db._state, False, traceback=tb)
|
|
239
|
-
fe = MigrationError(
|
|
240
|
-
"Error while executing migration", output=out, exc=e, traceback=tb
|
|
241
|
-
)
|
|
242
|
-
if hasattr(self.module, "rollback"):
|
|
243
|
-
try:
|
|
244
|
-
self.module.rollback(db)
|
|
245
|
-
out = _extract_output(q)
|
|
246
|
-
self.add_record("rollback", out, db._state, True)
|
|
247
|
-
msg = "Error while executing migration, rollback has been applied"
|
|
248
|
-
fe = RollbackError(msg, output=out, migrate_exc=fe)
|
|
249
|
-
except Exception as re:
|
|
250
|
-
out = _extract_output(q)
|
|
251
|
-
self.add_record("rollback", out, db._state, False)
|
|
252
|
-
msg = "Error while executing migration rollback"
|
|
253
|
-
fe = RollbackError(msg, output=out, exc=re, migrate_exc=fe)
|
|
254
|
-
raise fe
|
|
255
|
-
|
|
256
|
-
if not dryrun:
|
|
257
|
-
self.add_record("migrate", out, state, True)
|
|
258
|
-
|
|
259
|
-
return out
|
|
260
|
-
|
|
261
|
-
def unrecord(self):
|
|
262
|
-
"""Delete a migration record"""
|
|
263
|
-
if not self.record.exists():
|
|
264
|
-
return False
|
|
265
|
-
return bool(self.collection.delete_one(self.db_query).deleted_count)
|
|
266
|
-
|
|
267
|
-
def add_record(self, type, output, state, success, traceback=None):
|
|
268
|
-
script = inspect.getsource(self.module)
|
|
269
|
-
return Record(
|
|
270
|
-
self.collection.find_one_and_update(
|
|
271
|
-
self.db_query,
|
|
272
|
-
{
|
|
273
|
-
"$push": {
|
|
274
|
-
"ops": {
|
|
275
|
-
"date": datetime.utcnow(),
|
|
276
|
-
"type": type,
|
|
277
|
-
"script": script,
|
|
278
|
-
"output": output,
|
|
279
|
-
"state": state,
|
|
280
|
-
"success": success,
|
|
281
|
-
"traceback": traceback,
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
upsert=True,
|
|
286
|
-
return_document=ReturnDocument.AFTER,
|
|
287
|
-
)
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
def get(plugin, filename):
|
|
292
|
-
"""Get a migration"""
|
|
293
|
-
return Migration(plugin, filename)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
def list_available():
|
|
297
|
-
"""
|
|
298
|
-
List available migrations for udata and enabled plugins
|
|
299
|
-
|
|
300
|
-
Each row is a tuple with following signature:
|
|
301
|
-
|
|
302
|
-
(plugin, package, filename)
|
|
303
|
-
"""
|
|
304
|
-
migrations = []
|
|
305
|
-
|
|
306
|
-
migrations.extend(_iter("udata", "udata"))
|
|
307
|
-
|
|
308
|
-
plugins = entrypoints.get_enabled("udata.models", current_app)
|
|
309
|
-
for plugin, module in plugins.items():
|
|
310
|
-
migrations.extend(_iter(plugin, module))
|
|
311
|
-
return sorted(migrations, key=lambda m: m.filename)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
def _iter(plugin, module):
|
|
315
|
-
"""
|
|
316
|
-
Iterate over migrations for a given plugin module
|
|
317
|
-
|
|
318
|
-
Yield tuples in the form (plugin_name, module_name, filename)
|
|
319
|
-
"""
|
|
320
|
-
module_name = module if isinstance(module, str) else module.__name__
|
|
321
|
-
if not resource_isdir(module_name, "migrations"):
|
|
322
|
-
return
|
|
323
|
-
for filename in resource_listdir(module_name, "migrations"):
|
|
324
|
-
if filename.endswith(".py") and not filename.startswith("__"):
|
|
325
|
-
yield Migration(plugin, filename, module_name)
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def _module_name(plugin):
|
|
329
|
-
"""Get the module name for a given plugin"""
|
|
330
|
-
if plugin == "udata":
|
|
331
|
-
return "udata"
|
|
332
|
-
module = entrypoints.get_plugin_module("udata.models", current_app, plugin)
|
|
333
|
-
if module is None:
|
|
334
|
-
raise MigrationError("Plugin {} not found".format(plugin))
|
|
335
|
-
return module.__name__
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
def load_migration(plugin, filename, module_name=None):
|
|
339
|
-
"""
|
|
340
|
-
Load a migration from its python file
|
|
341
|
-
|
|
342
|
-
:returns: the loaded module
|
|
343
|
-
"""
|
|
344
|
-
module_name = module_name or _module_name(plugin)
|
|
345
|
-
basename = os.path.splitext(os.path.basename(filename))[0]
|
|
346
|
-
name = ".".join((module_name, "migrations", basename))
|
|
347
|
-
filename = os.path.join("migrations", filename)
|
|
348
|
-
try:
|
|
349
|
-
script = resource_string(module_name, filename)
|
|
350
|
-
except Exception:
|
|
351
|
-
msg = "Unable to load file {} from module {}".format(filename, module_name)
|
|
352
|
-
raise MigrationError(msg)
|
|
353
|
-
spec = importlib.util.spec_from_loader(name, loader=None)
|
|
354
|
-
module = importlib.util.module_from_spec(spec)
|
|
355
|
-
exec(script, module.__dict__)
|
|
356
|
-
module.__file__ = resource_filename(module_name, filename)
|
|
357
|
-
return module
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
def _extract_output(q):
|
|
361
|
-
"""Extract log output from a QueueHandler queue"""
|
|
362
|
-
out = []
|
|
363
|
-
while not q.empty():
|
|
364
|
-
record = q.get()
|
|
365
|
-
# Use list instead of tuple to have the same data before and after mongo persist
|
|
366
|
-
out.append([record.levelname.lower(), record.getMessage()])
|
|
367
|
-
return out
|
udata/templates/mail/base.html
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
2
|
-
<html xmlns="http://www.w3.org/1999/xhtml" style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
3
|
-
<head>
|
|
4
|
-
<!-- If you delete this meta tag, Half Life 3 will never be released. -->
|
|
5
|
-
<meta name="viewport" content="width=device-width">
|
|
6
|
-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
7
|
-
<title>{{config.SITE_TITLE}} - {{subject}}</title>
|
|
8
|
-
<style>
|
|
9
|
-
/* -------------------------------------------
|
|
10
|
-
PHONE
|
|
11
|
-
For clients that support media queries.
|
|
12
|
-
Nothing fancy.
|
|
13
|
-
-------------------------------------------- */
|
|
14
|
-
@media only screen and (max-width: 600px) {
|
|
15
|
-
|
|
16
|
-
a[class="btn"] { display:block!important; margin-bottom:10px!important; background-image:none!important; margin-right:0!important;}
|
|
17
|
-
|
|
18
|
-
div[class="column"] { width: auto!important; float:none!important;}
|
|
19
|
-
|
|
20
|
-
table.social div[class="column"] {
|
|
21
|
-
width:auto!important;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
</style>
|
|
25
|
-
{% block head %}{% endblock %}
|
|
26
|
-
</head>
|
|
27
|
-
|
|
28
|
-
<body bgcolor="#efefef" style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;-webkit-font-smoothing: antialiased;-webkit-text-size-adjust: none;height: 100%;color: #373737;width: 100%!important;background-color: #efefef;">
|
|
29
|
-
|
|
30
|
-
<!-- HEADER -->
|
|
31
|
-
<table class="head-wrap" cellpadding="0" cellspacing="0" style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;width: 100%;">
|
|
32
|
-
<tr style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
33
|
-
<td style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;"></td>
|
|
34
|
-
<td class="header container" style="margin: 0 auto!important;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;display: block!important;max-width: 628px!important;clear: both!important;">
|
|
35
|
-
|
|
36
|
-
<div style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
37
|
-
<table cellpadding="0" cellspacing="0" style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
38
|
-
<tr style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
39
|
-
<td align="center" class="head-message" style="margin: 0;padding: 16px;font-family: arial,helvetica,sans-serif;color: #888888;font-size: 11px;">
|
|
40
|
-
{% if browser_url %}
|
|
41
|
-
{% set link =
|
|
42
|
-
'<a href="'|safe
|
|
43
|
-
+ browser_link
|
|
44
|
-
+ '"Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;color: #2081c5;text-decoration: none;"> '|safe
|
|
45
|
-
+ _('on your browser.')
|
|
46
|
-
+ '</a>'|safe
|
|
47
|
-
%}
|
|
48
|
-
{{ _('View this email %(link)s', link=link) }}
|
|
49
|
-
{% else %}
|
|
50
|
-
|
|
51
|
-
{% endif %}
|
|
52
|
-
</td>
|
|
53
|
-
</tr>
|
|
54
|
-
</table>
|
|
55
|
-
</div>
|
|
56
|
-
|
|
57
|
-
</td>
|
|
58
|
-
<td style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;"></td>
|
|
59
|
-
</tr>
|
|
60
|
-
</table><!-- /HEADER -->
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<!-- BODY -->
|
|
64
|
-
<table class="body-wrap" cellpadding="0" cellspacing="0" style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;width: 100%;">
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<tr style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
69
|
-
<td style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;"></td>
|
|
70
|
-
<td class="container" bgcolor="#FFFFFF" style="margin: 0 auto!important;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;display: block!important;max-width: 628px!important;clear: both!important;">
|
|
71
|
-
|
|
72
|
-
<div class="content" style="margin: 0 auto;padding: 35px 47px;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;max-width: 628px;display: block;">
|
|
73
|
-
<table style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;width: 100%;">
|
|
74
|
-
<tr style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
75
|
-
<td style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;">
|
|
76
|
-
<h3 style="margin: 0;padding: 0;font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;line-height: 1.1;margin-bottom: 15px;color: #000;font-weight: 500;font-size: 24px;">{{ _('Hi %(user)s', user=recipient.first_name) }}</h3>
|
|
77
|
-
</td>
|
|
78
|
-
</tr>
|
|
79
|
-
<tr style="margin: 0;padding: 0;">
|
|
80
|
-
<td style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;margin-bottom: 20px;font-weight: normal;font-size: 14px;line-height: 1.5;">
|
|
81
|
-
{% block body %}{% endblock %}
|
|
82
|
-
</td>
|
|
83
|
-
</tr>
|
|
84
|
-
|
|
85
|
-
<tr style="margin: 0;padding: 0;">
|
|
86
|
-
<td style="margin: 0;padding: 0;">
|
|
87
|
-
<p style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;margin-top: 20px;margin-bottom: 20px;font-weight: normal;font-size: 14px;line-height: 1.5;">{{ _('See you soon on the platform') }}<br/>
|
|
88
|
-
<i>{{ _('The %(site)s team', site=config.SITE_TITLE) }}</i></p>
|
|
89
|
-
</td>
|
|
90
|
-
</tr>
|
|
91
|
-
|
|
92
|
-
</table>
|
|
93
|
-
|
|
94
|
-
</div></td>
|
|
95
|
-
</tr>
|
|
96
|
-
</table>
|
|
97
|
-
<!-- /content -->
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
<td style="margin: 0;padding: 0;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;"></td>
|
|
101
|
-
|
|
102
|
-
<!-- /BODY -->
|
|
103
|
-
|
|
104
|
-
</body>
|
|
105
|
-
</html>
|
udata/templates/mail/base.txt
DELETED
udata/templates/mail/button.html
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
{% macro mail_button(label, url) %}
|
|
2
|
-
<a class="btn" href="{{ url }}" style="margin: 30px 15px;padding: 8px 20px;font-family: "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;color: #ffffff;text-decoration: none;background-color: #438fc5;font-weight: bold;text-align: center;cursor: pointer;display: inline-block;font-size: 11px;text-transform: uppercase;">{{ label }}</a>
|
|
3
|
-
{% endmacro %}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{% extends 'base.html' %}
|
|
2
|
-
{% from 'macros/breadcrumb.html' import breadcrumb with context %}
|
|
3
|
-
{% set section_class = section_class|default('default') %}
|
|
4
|
-
{% set toolbar_class = toolbar_class|default('') %}
|
|
5
|
-
|
|
6
|
-
{% block content %}
|
|
7
|
-
{{ breadcrumb(self,
|
|
8
|
-
breadcrum_class=breadcrum_class,
|
|
9
|
-
toolbar_class=toolbar_class
|
|
10
|
-
) }}
|
|
11
|
-
|
|
12
|
-
<section class="{{ section_class }}">
|
|
13
|
-
<div class="{{ container_class }}">
|
|
14
|
-
{% block main_content %}{% endblock %}
|
|
15
|
-
</div>
|
|
16
|
-
</section>
|
|
17
|
-
|
|
18
|
-
{% block extra_sections %}{% endblock %}
|
|
19
|
-
{% endblock %}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
{% extends 'base.html' %}
|
|
2
|
-
{% from 'macros/breadcrumb.html' import breadcrumb with context %}
|
|
3
|
-
{% set section_class = section_class|default('default') %}
|
|
4
|
-
|
|
5
|
-
{% block content %}
|
|
6
|
-
{{ breadcrumb(self) }}
|
|
7
|
-
|
|
8
|
-
<section class="{{ section_class }}">
|
|
9
|
-
<div class="container {{ container_class }}">
|
|
10
|
-
<div class="row">
|
|
11
|
-
<div class="col-lg-8">
|
|
12
|
-
{% block main_content %}{% endblock %}
|
|
13
|
-
</div>
|
|
14
|
-
<aside class="col-lg-4">
|
|
15
|
-
{% block sidebar %}{% endblock %}
|
|
16
|
-
</aside>
|
|
17
|
-
</div>
|
|
18
|
-
</div>
|
|
19
|
-
</section>
|
|
20
|
-
{% endblock %}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{% extends 'raw.html' %}
|
|
2
|
-
|
|
3
|
-
{% set body_class = 'panel-center' %}
|
|
4
|
-
|
|
5
|
-
{% block body %}
|
|
6
|
-
<div class="container">
|
|
7
|
-
<div class="row">
|
|
8
|
-
<div class="col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2">
|
|
9
|
-
<div class="panel panel-default">
|
|
10
|
-
{% block panel_content %}
|
|
11
|
-
{% endblock %}
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
{% endblock %}
|
udata/tests/cli/test_db_cli.py
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from bson import ObjectId
|
|
5
|
-
from mongoengine.connection import get_db
|
|
6
|
-
|
|
7
|
-
from udata.models import Reuse
|
|
8
|
-
from udata.tests.api import PytestOnlyDBTestCase
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
@pytest.fixture
|
|
12
|
-
def migrations():
|
|
13
|
-
get_db().migrations.insert_one(
|
|
14
|
-
{
|
|
15
|
-
"plugin": "udata",
|
|
16
|
-
"filename": "test.py",
|
|
17
|
-
"date": datetime.utcnow(),
|
|
18
|
-
"script": 'print("ok")',
|
|
19
|
-
"output": "ok",
|
|
20
|
-
}
|
|
21
|
-
)
|
|
22
|
-
return get_db().migrations
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class DBCliTest(PytestOnlyDBTestCase):
|
|
26
|
-
def test_unrecord_with_complete_filename(self, cli, migrations):
|
|
27
|
-
"""Should display help without errors"""
|
|
28
|
-
result = cli("db unrecord udata test.py")
|
|
29
|
-
assert result.exit_code == 0
|
|
30
|
-
assert migrations.count_documents({}) == 0
|
|
31
|
-
|
|
32
|
-
def test_unrecord_with_filename_without_extension(self, cli, migrations):
|
|
33
|
-
"""Should display help without errors"""
|
|
34
|
-
result = cli("db unrecord udata test")
|
|
35
|
-
assert result.exit_code == 0
|
|
36
|
-
assert migrations.count_documents({}) == 0
|
|
37
|
-
|
|
38
|
-
def test_unrecord_with_single_parameter(self, cli, migrations):
|
|
39
|
-
"""Should display help without errors"""
|
|
40
|
-
result = cli("db unrecord udata:test.py")
|
|
41
|
-
assert result.exit_code == 0
|
|
42
|
-
assert migrations.count_documents({}) == 0
|
|
43
|
-
|
|
44
|
-
def test_unrecord_with_single_parameter_without_extension(self, cli, migrations):
|
|
45
|
-
"""Should display help without errors"""
|
|
46
|
-
result = cli("db unrecord udata:test")
|
|
47
|
-
assert result.exit_code == 0
|
|
48
|
-
assert migrations.count_documents({}) == 0
|
|
49
|
-
|
|
50
|
-
def test_unrecord_without_parameters(self, cli, migrations):
|
|
51
|
-
"""Should display help without errors"""
|
|
52
|
-
result = cli("db unrecord", check=False)
|
|
53
|
-
assert result.exit_code != 0
|
|
54
|
-
assert migrations.count_documents({}) == 1
|
|
55
|
-
|
|
56
|
-
def test_unrecord_with_too_many_parameters(self, cli, migrations):
|
|
57
|
-
"""Should display help without errors"""
|
|
58
|
-
result = cli("db unrecord udata test.py too many", check=False)
|
|
59
|
-
assert result.exit_code != 0
|
|
60
|
-
assert migrations.count_documents({}) == 1
|
|
61
|
-
|
|
62
|
-
def test_check_references_report_listfield_missing(self, cli):
|
|
63
|
-
# The cli command `udata db check-integrity` should catch reuse object missing datasets field
|
|
64
|
-
Reuse._get_collection().insert_one({"_id": ObjectId()})
|
|
65
|
-
|
|
66
|
-
result = cli("db check-integrity --models Reuse", check=False)
|
|
67
|
-
assert "Reuse.datasets(Dataset) — list…: 1" in result.output
|
|
68
|
-
assert result.exit_code != 0
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from udata.core.spatial.factories import GeoZoneFactory
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
def create_geozones_fixtures():
|
|
5
|
-
paca = GeoZoneFactory(
|
|
6
|
-
id="fr:region:93", level="fr:region", name="Provence Alpes Côtes dAzur", code="93"
|
|
7
|
-
)
|
|
8
|
-
bdr = GeoZoneFactory(
|
|
9
|
-
id="fr:departement:13", level="fr:departement", name="Bouches-du-Rhône", code="13"
|
|
10
|
-
)
|
|
11
|
-
arles = GeoZoneFactory(id="fr:commune:13004", level="fr:commune", name="Arles", code="13004")
|
|
12
|
-
return paca, bdr, arles
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
def create_old_new_regions_fixtures():
|
|
16
|
-
lr = GeoZoneFactory(id="fr:region:91", level="fr:region", name="Languedoc-Rousillon", code="91")
|
|
17
|
-
occitanie = GeoZoneFactory(
|
|
18
|
-
id="fr:region:76", level="fr:region", name="Languedoc-Rousillon et Midi-Pyrénées", code="76"
|
|
19
|
-
)
|
|
20
|
-
return lr, occitanie
|