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,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
@@ -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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;width: 100%;">
32
- <tr style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;">
33
- <td style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;"></td>
34
- <td class="header container" style="margin: 0 auto!important;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;display: block!important;max-width: 628px!important;clear: both!important;">
35
-
36
- <div style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;">
37
- <table cellpadding="0" cellspacing="0" style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;">
38
- <tr style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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
- + '&quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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
- &nbsp;
51
- {% endif %}
52
- </td>
53
- </tr>
54
- </table>
55
- </div>
56
-
57
- </td>
58
- <td style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;width: 100%;">
65
-
66
-
67
-
68
- <tr style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;">
69
- <td style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;"></td>
70
- <td class="container" bgcolor="#FFFFFF" style="margin: 0 auto!important;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;max-width: 628px;display: block;">
73
- <table style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;width: 100%;">
74
- <tr style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;">
75
- <td style="margin: 0;padding: 0;font-family: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;">
76
- <h3 style="margin: 0;padding: 0;font-family: &quot;HelveticaNeue-Light&quot;, &quot;Helvetica Neue Light&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, &quot;Lucida Grande&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, Helvetica, Arial, sans-serif;"></td>
101
-
102
- <!-- /BODY -->
103
-
104
- </body>
105
- </html>
@@ -1,6 +0,0 @@
1
- {{ _('Hi %(user)s', user=recipient.first_name) }}
2
-
3
- {% block body %}{% endblock %}
4
-
5
- {{ _('See you soon on the platform') }}
6
- {{ _('The %(site)s team', site=config.SITE_TITLE) }}
@@ -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: &quot;Helvetica Neue&quot;, &quot;Helvetica&quot;, 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 %}
@@ -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