udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30454__py2.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 (413) hide show
  1. tasks/__init__.py +109 -107
  2. tasks/helpers.py +18 -18
  3. udata/__init__.py +4 -4
  4. udata/admin/views.py +5 -5
  5. udata/api/__init__.py +111 -134
  6. udata/api/commands.py +45 -37
  7. udata/api/errors.py +5 -4
  8. udata/api/fields.py +23 -21
  9. udata/api/oauth2.py +55 -74
  10. udata/api/parsers.py +15 -15
  11. udata/api/signals.py +1 -1
  12. udata/api_fields.py +137 -89
  13. udata/app.py +58 -55
  14. udata/assets.py +5 -5
  15. udata/auth/__init__.py +37 -26
  16. udata/auth/forms.py +23 -15
  17. udata/auth/helpers.py +1 -1
  18. udata/auth/mails.py +3 -3
  19. udata/auth/password_validation.py +19 -15
  20. udata/auth/views.py +94 -68
  21. udata/commands/__init__.py +71 -69
  22. udata/commands/cache.py +7 -7
  23. udata/commands/db.py +201 -140
  24. udata/commands/dcat.py +36 -30
  25. udata/commands/fixtures.py +100 -84
  26. udata/commands/images.py +21 -20
  27. udata/commands/info.py +17 -20
  28. udata/commands/init.py +10 -10
  29. udata/commands/purge.py +12 -13
  30. udata/commands/serve.py +41 -29
  31. udata/commands/static.py +16 -18
  32. udata/commands/test.py +20 -20
  33. udata/commands/tests/fixtures.py +26 -24
  34. udata/commands/worker.py +31 -33
  35. udata/core/__init__.py +12 -12
  36. udata/core/activity/__init__.py +0 -1
  37. udata/core/activity/api.py +59 -49
  38. udata/core/activity/models.py +28 -26
  39. udata/core/activity/signals.py +1 -1
  40. udata/core/activity/tasks.py +16 -10
  41. udata/core/badges/api.py +6 -6
  42. udata/core/badges/commands.py +14 -13
  43. udata/core/badges/fields.py +8 -5
  44. udata/core/badges/forms.py +7 -4
  45. udata/core/badges/models.py +16 -31
  46. udata/core/badges/permissions.py +1 -3
  47. udata/core/badges/signals.py +2 -2
  48. udata/core/badges/tasks.py +3 -2
  49. udata/core/badges/tests/test_commands.py +10 -10
  50. udata/core/badges/tests/test_model.py +24 -31
  51. udata/core/contact_point/api.py +19 -18
  52. udata/core/contact_point/api_fields.py +21 -14
  53. udata/core/contact_point/factories.py +2 -2
  54. udata/core/contact_point/forms.py +7 -6
  55. udata/core/contact_point/models.py +3 -5
  56. udata/core/dataservices/api.py +26 -21
  57. udata/core/dataservices/factories.py +13 -11
  58. udata/core/dataservices/models.py +35 -40
  59. udata/core/dataservices/permissions.py +4 -4
  60. udata/core/dataservices/rdf.py +40 -17
  61. udata/core/dataservices/tasks.py +4 -3
  62. udata/core/dataset/actions.py +10 -10
  63. udata/core/dataset/activities.py +21 -23
  64. udata/core/dataset/api.py +321 -298
  65. udata/core/dataset/api_fields.py +443 -271
  66. udata/core/dataset/apiv2.py +305 -229
  67. udata/core/dataset/commands.py +38 -36
  68. udata/core/dataset/constants.py +61 -54
  69. udata/core/dataset/csv.py +70 -74
  70. udata/core/dataset/events.py +39 -32
  71. udata/core/dataset/exceptions.py +8 -4
  72. udata/core/dataset/factories.py +57 -65
  73. udata/core/dataset/forms.py +87 -63
  74. udata/core/dataset/models.py +336 -280
  75. udata/core/dataset/permissions.py +9 -6
  76. udata/core/dataset/preview.py +15 -17
  77. udata/core/dataset/rdf.py +156 -122
  78. udata/core/dataset/search.py +92 -77
  79. udata/core/dataset/signals.py +1 -1
  80. udata/core/dataset/tasks.py +63 -54
  81. udata/core/discussions/actions.py +5 -5
  82. udata/core/discussions/api.py +124 -120
  83. udata/core/discussions/factories.py +2 -2
  84. udata/core/discussions/forms.py +9 -7
  85. udata/core/discussions/metrics.py +1 -3
  86. udata/core/discussions/models.py +25 -24
  87. udata/core/discussions/notifications.py +18 -14
  88. udata/core/discussions/permissions.py +3 -3
  89. udata/core/discussions/signals.py +4 -4
  90. udata/core/discussions/tasks.py +24 -28
  91. udata/core/followers/api.py +32 -33
  92. udata/core/followers/models.py +9 -9
  93. udata/core/followers/signals.py +3 -3
  94. udata/core/jobs/actions.py +7 -7
  95. udata/core/jobs/api.py +99 -92
  96. udata/core/jobs/commands.py +48 -49
  97. udata/core/jobs/forms.py +11 -11
  98. udata/core/jobs/models.py +6 -6
  99. udata/core/metrics/__init__.py +2 -2
  100. udata/core/metrics/commands.py +34 -30
  101. udata/core/metrics/models.py +2 -4
  102. udata/core/metrics/signals.py +1 -1
  103. udata/core/metrics/tasks.py +3 -3
  104. udata/core/organization/activities.py +12 -15
  105. udata/core/organization/api.py +167 -174
  106. udata/core/organization/api_fields.py +183 -124
  107. udata/core/organization/apiv2.py +32 -32
  108. udata/core/organization/commands.py +20 -22
  109. udata/core/organization/constants.py +11 -11
  110. udata/core/organization/csv.py +17 -15
  111. udata/core/organization/factories.py +8 -11
  112. udata/core/organization/forms.py +32 -26
  113. udata/core/organization/metrics.py +2 -1
  114. udata/core/organization/models.py +87 -67
  115. udata/core/organization/notifications.py +18 -14
  116. udata/core/organization/permissions.py +10 -11
  117. udata/core/organization/rdf.py +14 -14
  118. udata/core/organization/search.py +30 -28
  119. udata/core/organization/signals.py +7 -7
  120. udata/core/organization/tasks.py +42 -61
  121. udata/core/owned.py +38 -27
  122. udata/core/post/api.py +82 -81
  123. udata/core/post/constants.py +8 -5
  124. udata/core/post/factories.py +4 -4
  125. udata/core/post/forms.py +13 -14
  126. udata/core/post/models.py +20 -22
  127. udata/core/post/tests/test_api.py +30 -32
  128. udata/core/reports/api.py +8 -7
  129. udata/core/reports/constants.py +1 -3
  130. udata/core/reports/models.py +10 -10
  131. udata/core/reuse/activities.py +15 -19
  132. udata/core/reuse/api.py +123 -126
  133. udata/core/reuse/api_fields.py +120 -85
  134. udata/core/reuse/apiv2.py +11 -10
  135. udata/core/reuse/constants.py +23 -23
  136. udata/core/reuse/csv.py +18 -18
  137. udata/core/reuse/factories.py +5 -9
  138. udata/core/reuse/forms.py +24 -21
  139. udata/core/reuse/models.py +55 -51
  140. udata/core/reuse/permissions.py +2 -2
  141. udata/core/reuse/search.py +49 -46
  142. udata/core/reuse/signals.py +1 -1
  143. udata/core/reuse/tasks.py +4 -5
  144. udata/core/site/api.py +47 -50
  145. udata/core/site/factories.py +2 -2
  146. udata/core/site/forms.py +4 -5
  147. udata/core/site/models.py +94 -63
  148. udata/core/site/rdf.py +14 -14
  149. udata/core/spam/api.py +16 -9
  150. udata/core/spam/constants.py +4 -4
  151. udata/core/spam/fields.py +13 -7
  152. udata/core/spam/models.py +27 -20
  153. udata/core/spam/signals.py +1 -1
  154. udata/core/spam/tests/test_spam.py +6 -5
  155. udata/core/spatial/api.py +72 -80
  156. udata/core/spatial/api_fields.py +73 -58
  157. udata/core/spatial/commands.py +67 -64
  158. udata/core/spatial/constants.py +3 -3
  159. udata/core/spatial/factories.py +37 -54
  160. udata/core/spatial/forms.py +27 -26
  161. udata/core/spatial/geoids.py +17 -17
  162. udata/core/spatial/models.py +43 -47
  163. udata/core/spatial/tasks.py +2 -1
  164. udata/core/spatial/tests/test_api.py +115 -130
  165. udata/core/spatial/tests/test_fields.py +74 -77
  166. udata/core/spatial/tests/test_geoid.py +22 -22
  167. udata/core/spatial/tests/test_models.py +5 -7
  168. udata/core/spatial/translations.py +16 -16
  169. udata/core/storages/__init__.py +16 -18
  170. udata/core/storages/api.py +66 -64
  171. udata/core/storages/tasks.py +7 -7
  172. udata/core/storages/utils.py +15 -15
  173. udata/core/storages/views.py +5 -6
  174. udata/core/tags/api.py +17 -14
  175. udata/core/tags/csv.py +4 -4
  176. udata/core/tags/models.py +8 -5
  177. udata/core/tags/tasks.py +11 -13
  178. udata/core/tags/views.py +4 -4
  179. udata/core/topic/api.py +84 -73
  180. udata/core/topic/apiv2.py +157 -127
  181. udata/core/topic/factories.py +3 -4
  182. udata/core/topic/forms.py +12 -14
  183. udata/core/topic/models.py +14 -19
  184. udata/core/topic/parsers.py +26 -26
  185. udata/core/user/activities.py +30 -29
  186. udata/core/user/api.py +151 -152
  187. udata/core/user/api_fields.py +132 -100
  188. udata/core/user/apiv2.py +7 -7
  189. udata/core/user/commands.py +38 -38
  190. udata/core/user/factories.py +8 -9
  191. udata/core/user/forms.py +14 -11
  192. udata/core/user/metrics.py +2 -2
  193. udata/core/user/models.py +68 -69
  194. udata/core/user/permissions.py +4 -5
  195. udata/core/user/rdf.py +7 -8
  196. udata/core/user/tasks.py +2 -2
  197. udata/core/user/tests/test_user_model.py +24 -16
  198. udata/cors.py +99 -0
  199. udata/db/tasks.py +2 -1
  200. udata/entrypoints.py +35 -31
  201. udata/errors.py +2 -1
  202. udata/event/values.py +6 -6
  203. udata/factories.py +2 -2
  204. udata/features/identicon/api.py +5 -6
  205. udata/features/identicon/backends.py +48 -55
  206. udata/features/identicon/tests/test_backends.py +4 -5
  207. udata/features/notifications/__init__.py +0 -1
  208. udata/features/notifications/actions.py +9 -9
  209. udata/features/notifications/api.py +17 -13
  210. udata/features/territories/__init__.py +12 -10
  211. udata/features/territories/api.py +14 -15
  212. udata/features/territories/models.py +23 -28
  213. udata/features/transfer/actions.py +8 -11
  214. udata/features/transfer/api.py +84 -77
  215. udata/features/transfer/factories.py +2 -1
  216. udata/features/transfer/models.py +11 -12
  217. udata/features/transfer/notifications.py +19 -15
  218. udata/features/transfer/permissions.py +5 -5
  219. udata/forms/__init__.py +5 -2
  220. udata/forms/fields.py +164 -172
  221. udata/forms/validators.py +19 -22
  222. udata/forms/widgets.py +9 -13
  223. udata/frontend/__init__.py +31 -26
  224. udata/frontend/csv.py +68 -58
  225. udata/frontend/markdown.py +40 -44
  226. udata/harvest/actions.py +89 -77
  227. udata/harvest/api.py +294 -238
  228. udata/harvest/backends/__init__.py +4 -4
  229. udata/harvest/backends/base.py +128 -111
  230. udata/harvest/backends/dcat.py +80 -66
  231. udata/harvest/commands.py +56 -60
  232. udata/harvest/csv.py +8 -8
  233. udata/harvest/exceptions.py +6 -3
  234. udata/harvest/filters.py +24 -23
  235. udata/harvest/forms.py +27 -28
  236. udata/harvest/models.py +88 -80
  237. udata/harvest/notifications.py +15 -10
  238. udata/harvest/signals.py +13 -13
  239. udata/harvest/tasks.py +11 -10
  240. udata/harvest/tests/factories.py +23 -24
  241. udata/harvest/tests/test_actions.py +136 -166
  242. udata/harvest/tests/test_api.py +220 -214
  243. udata/harvest/tests/test_base_backend.py +117 -112
  244. udata/harvest/tests/test_dcat_backend.py +380 -308
  245. udata/harvest/tests/test_filters.py +33 -22
  246. udata/harvest/tests/test_models.py +11 -14
  247. udata/harvest/tests/test_notifications.py +6 -7
  248. udata/harvest/tests/test_tasks.py +7 -6
  249. udata/i18n.py +237 -78
  250. udata/linkchecker/backends.py +5 -11
  251. udata/linkchecker/checker.py +23 -22
  252. udata/linkchecker/commands.py +4 -6
  253. udata/linkchecker/models.py +6 -6
  254. udata/linkchecker/tasks.py +18 -20
  255. udata/mail.py +21 -21
  256. udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
  257. udata/migrations/2020-08-24-add-fs-filename.py +9 -8
  258. udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
  259. udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
  260. udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
  261. udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
  262. udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
  263. udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
  264. udata/migrations/2021-08-17-follow-integrity.py +5 -4
  265. udata/migrations/2021-08-17-harvest-integrity.py +13 -12
  266. udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
  267. udata/migrations/2021-08-17-transfer-integrity.py +5 -4
  268. udata/migrations/2021-08-17-users-integrity.py +9 -8
  269. udata/migrations/2021-12-14-reuse-topics.py +7 -6
  270. udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
  271. udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
  272. udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
  273. udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
  274. udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
  275. udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
  276. udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
  277. udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
  278. udata/migrations/__init__.py +123 -105
  279. udata/models/__init__.py +4 -4
  280. udata/mongo/__init__.py +13 -11
  281. udata/mongo/badges_field.py +3 -2
  282. udata/mongo/datetime_fields.py +13 -12
  283. udata/mongo/document.py +17 -16
  284. udata/mongo/engine.py +15 -16
  285. udata/mongo/errors.py +2 -1
  286. udata/mongo/extras_fields.py +30 -20
  287. udata/mongo/queryset.py +12 -12
  288. udata/mongo/slug_fields.py +38 -28
  289. udata/mongo/taglist_field.py +1 -2
  290. udata/mongo/url_field.py +5 -5
  291. udata/mongo/uuid_fields.py +4 -3
  292. udata/notifications/__init__.py +1 -1
  293. udata/notifications/mattermost.py +10 -9
  294. udata/rdf.py +167 -188
  295. udata/routing.py +40 -45
  296. udata/search/__init__.py +18 -19
  297. udata/search/adapter.py +17 -16
  298. udata/search/commands.py +44 -51
  299. udata/search/fields.py +13 -20
  300. udata/search/query.py +23 -18
  301. udata/search/result.py +9 -10
  302. udata/sentry.py +21 -19
  303. udata/settings.py +262 -198
  304. udata/sitemap.py +8 -6
  305. udata/storage/s3.py +20 -13
  306. udata/tags.py +4 -5
  307. udata/tasks.py +43 -42
  308. udata/tests/__init__.py +9 -6
  309. udata/tests/api/__init__.py +8 -6
  310. udata/tests/api/test_auth_api.py +395 -321
  311. udata/tests/api/test_base_api.py +33 -35
  312. udata/tests/api/test_contact_points.py +7 -9
  313. udata/tests/api/test_dataservices_api.py +211 -158
  314. udata/tests/api/test_datasets_api.py +823 -812
  315. udata/tests/api/test_follow_api.py +13 -15
  316. udata/tests/api/test_me_api.py +95 -112
  317. udata/tests/api/test_organizations_api.py +301 -339
  318. udata/tests/api/test_reports_api.py +35 -25
  319. udata/tests/api/test_reuses_api.py +134 -139
  320. udata/tests/api/test_swagger.py +5 -5
  321. udata/tests/api/test_tags_api.py +18 -25
  322. udata/tests/api/test_topics_api.py +94 -94
  323. udata/tests/api/test_transfer_api.py +53 -48
  324. udata/tests/api/test_user_api.py +128 -141
  325. udata/tests/apiv2/test_datasets.py +290 -198
  326. udata/tests/apiv2/test_me_api.py +10 -11
  327. udata/tests/apiv2/test_organizations.py +56 -74
  328. udata/tests/apiv2/test_swagger.py +5 -5
  329. udata/tests/apiv2/test_topics.py +69 -87
  330. udata/tests/cli/test_cli_base.py +8 -8
  331. udata/tests/cli/test_db_cli.py +21 -19
  332. udata/tests/dataservice/test_dataservice_tasks.py +8 -12
  333. udata/tests/dataset/test_csv_adapter.py +44 -35
  334. udata/tests/dataset/test_dataset_actions.py +2 -3
  335. udata/tests/dataset/test_dataset_commands.py +7 -8
  336. udata/tests/dataset/test_dataset_events.py +36 -29
  337. udata/tests/dataset/test_dataset_model.py +224 -217
  338. udata/tests/dataset/test_dataset_rdf.py +142 -131
  339. udata/tests/dataset/test_dataset_tasks.py +15 -15
  340. udata/tests/dataset/test_resource_preview.py +10 -13
  341. udata/tests/features/territories/__init__.py +9 -13
  342. udata/tests/features/territories/test_territories_api.py +71 -91
  343. udata/tests/forms/test_basic_fields.py +7 -7
  344. udata/tests/forms/test_current_user_field.py +39 -66
  345. udata/tests/forms/test_daterange_field.py +31 -39
  346. udata/tests/forms/test_dict_field.py +28 -26
  347. udata/tests/forms/test_extras_fields.py +102 -76
  348. udata/tests/forms/test_form_field.py +8 -8
  349. udata/tests/forms/test_image_field.py +33 -26
  350. udata/tests/forms/test_model_field.py +134 -123
  351. udata/tests/forms/test_model_list_field.py +7 -7
  352. udata/tests/forms/test_nested_model_list_field.py +117 -79
  353. udata/tests/forms/test_publish_as_field.py +36 -65
  354. udata/tests/forms/test_reference_field.py +34 -53
  355. udata/tests/forms/test_user_forms.py +23 -21
  356. udata/tests/forms/test_uuid_field.py +6 -10
  357. udata/tests/frontend/__init__.py +9 -6
  358. udata/tests/frontend/test_auth.py +7 -6
  359. udata/tests/frontend/test_csv.py +81 -96
  360. udata/tests/frontend/test_hooks.py +43 -43
  361. udata/tests/frontend/test_markdown.py +211 -191
  362. udata/tests/helpers.py +32 -37
  363. udata/tests/models.py +2 -2
  364. udata/tests/organization/test_csv_adapter.py +21 -16
  365. udata/tests/organization/test_notifications.py +11 -18
  366. udata/tests/organization/test_organization_model.py +13 -13
  367. udata/tests/organization/test_organization_rdf.py +29 -22
  368. udata/tests/organization/test_organization_tasks.py +16 -17
  369. udata/tests/plugin.py +79 -73
  370. udata/tests/reuse/test_reuse_model.py +21 -21
  371. udata/tests/reuse/test_reuse_task.py +11 -13
  372. udata/tests/search/__init__.py +11 -12
  373. udata/tests/search/test_adapter.py +60 -70
  374. udata/tests/search/test_query.py +16 -16
  375. udata/tests/search/test_results.py +10 -7
  376. udata/tests/site/test_site_api.py +11 -16
  377. udata/tests/site/test_site_metrics.py +20 -30
  378. udata/tests/site/test_site_model.py +4 -5
  379. udata/tests/site/test_site_rdf.py +94 -78
  380. udata/tests/test_activity.py +17 -17
  381. udata/tests/test_cors.py +62 -0
  382. udata/tests/test_discussions.py +292 -299
  383. udata/tests/test_i18n.py +37 -40
  384. udata/tests/test_linkchecker.py +91 -85
  385. udata/tests/test_mail.py +13 -17
  386. udata/tests/test_migrations.py +219 -180
  387. udata/tests/test_model.py +164 -157
  388. udata/tests/test_notifications.py +17 -17
  389. udata/tests/test_owned.py +14 -14
  390. udata/tests/test_rdf.py +25 -23
  391. udata/tests/test_routing.py +89 -93
  392. udata/tests/test_storages.py +137 -128
  393. udata/tests/test_tags.py +44 -46
  394. udata/tests/test_topics.py +7 -7
  395. udata/tests/test_transfer.py +42 -49
  396. udata/tests/test_uris.py +160 -161
  397. udata/tests/test_utils.py +79 -71
  398. udata/tests/user/test_user_rdf.py +5 -9
  399. udata/tests/workers/test_jobs_commands.py +57 -58
  400. udata/tests/workers/test_tasks_routing.py +23 -29
  401. udata/tests/workers/test_workers_api.py +125 -131
  402. udata/tests/workers/test_workers_helpers.py +6 -6
  403. udata/tracking.py +4 -6
  404. udata/uris.py +45 -46
  405. udata/utils.py +68 -66
  406. udata/wsgi.py +1 -1
  407. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/METADATA +7 -3
  408. udata-9.1.2.dev30454.dist-info/RECORD +706 -0
  409. udata-9.1.2.dev30355.dist-info/RECORD +0 -704
  410. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/LICENSE +0 -0
  411. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/WHEEL +0 -0
  412. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/entry_points.txt +0 -0
  413. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/top_level.txt +0 -0
@@ -1,239 +1,314 @@
1
1
  import logging
2
- import mongoengine
3
2
 
4
- from flask import url_for, request, abort
3
+ import mongoengine
4
+ from flask import abort, request, url_for
5
5
  from flask_restx import marshal
6
6
 
7
7
  from udata import search
8
- from udata.api import apiv2, API, fields
9
- from udata.utils import multi_to_dict, get_by
10
-
8
+ from udata.api import API, apiv2, fields
9
+ from udata.core.contact_point.api_fields import contact_point_fields
11
10
  from udata.core.organization.api_fields import member_user_with_email_fields
11
+ from udata.core.spatial.api_fields import geojson
12
+ from udata.utils import get_by, multi_to_dict
13
+
14
+ from .api import ResourceMixin
12
15
  from .api_fields import (
13
16
  badge_fields,
14
- org_ref_fields,
15
- resource_fields,
16
- spatial_coverage_fields,
17
- temporal_coverage_fields,
18
- user_ref_fields,
17
+ catalog_schema_fields,
19
18
  checksum_fields,
20
19
  dataset_harvest_fields,
21
20
  dataset_internal_fields,
21
+ org_ref_fields,
22
+ resource_fields,
22
23
  resource_harvest_fields,
23
24
  resource_internal_fields,
24
- catalog_schema_fields,
25
- schema_fields
25
+ schema_fields,
26
+ spatial_coverage_fields,
27
+ temporal_coverage_fields,
28
+ user_ref_fields,
26
29
  )
27
- from udata.core.spatial.api_fields import geojson
28
- from udata.core.contact_point.api_fields import contact_point_fields
29
- from .models import Dataset, CommunityResource
30
- from .constants import UPDATE_FREQUENCIES, DEFAULT_FREQUENCY, DEFAULT_LICENSE
31
- from .api import ResourceMixin
30
+ from .constants import DEFAULT_FREQUENCY, DEFAULT_LICENSE, UPDATE_FREQUENCIES
31
+ from .models import CommunityResource, Dataset
32
32
  from .permissions import DatasetEditPermission, ResourceEditPermission
33
33
  from .search import DatasetSearch
34
34
 
35
35
  DEFAULT_PAGE_SIZE = 50
36
36
 
37
37
  #: Default mask to make it lightweight by default
38
- DEFAULT_MASK_APIV2 = ','.join((
39
- 'id', 'title', 'acronym', 'slug', 'description', 'created_at', 'last_modified', 'deleted',
40
- 'private', 'tags', 'badges', 'resources', 'community_resources', 'frequency', 'frequency_date',
41
- 'extras', 'metrics', 'organization', 'owner', 'temporal_coverage', 'spatial', 'license',
42
- 'uri', 'page', 'last_update', 'archived', 'quality', 'harvest', 'internal', 'contact_point',
43
- ))
38
+ DEFAULT_MASK_APIV2 = ",".join(
39
+ (
40
+ "id",
41
+ "title",
42
+ "acronym",
43
+ "slug",
44
+ "description",
45
+ "created_at",
46
+ "last_modified",
47
+ "deleted",
48
+ "private",
49
+ "tags",
50
+ "badges",
51
+ "resources",
52
+ "community_resources",
53
+ "frequency",
54
+ "frequency_date",
55
+ "extras",
56
+ "metrics",
57
+ "organization",
58
+ "owner",
59
+ "temporal_coverage",
60
+ "spatial",
61
+ "license",
62
+ "uri",
63
+ "page",
64
+ "last_update",
65
+ "archived",
66
+ "quality",
67
+ "harvest",
68
+ "internal",
69
+ "contact_point",
70
+ )
71
+ )
44
72
 
45
73
  log = logging.getLogger(__name__)
46
74
 
47
- ns = apiv2.namespace('datasets', 'Dataset related operations')
75
+ ns = apiv2.namespace("datasets", "Dataset related operations")
48
76
  search_parser = DatasetSearch.as_request_parser()
49
77
  resources_parser = apiv2.parser()
50
78
  resources_parser.add_argument(
51
- 'page', type=int, default=1, location='args', help='The page to fetch')
79
+ "page", type=int, default=1, location="args", help="The page to fetch"
80
+ )
52
81
  resources_parser.add_argument(
53
- 'page_size', type=int, default=DEFAULT_PAGE_SIZE, location='args',
54
- help='The page size to fetch')
82
+ "page_size", type=int, default=DEFAULT_PAGE_SIZE, location="args", help="The page size to fetch"
83
+ )
55
84
  resources_parser.add_argument(
56
- 'type', type=str, location='args',
57
- help='The type of resources to fetch')
85
+ "type", type=str, location="args", help="The type of resources to fetch"
86
+ )
58
87
  resources_parser.add_argument(
59
- 'q', type=str, location='args',
60
- help='query string to search through resources titles')
61
-
62
- common_doc = {
63
- 'params': {'dataset': 'The dataset ID or slug'}
64
- }
65
-
66
-
67
- dataset_fields = apiv2.model('Dataset', {
68
- 'id': fields.String(description='The dataset identifier', readonly=True),
69
- 'title': fields.String(description='The dataset title', required=True),
70
- 'acronym': fields.String(description='An optional dataset acronym'),
71
- 'slug': fields.String(
72
- description='The dataset permalink string', required=True),
73
- 'description': fields.Markdown(
74
- description='The dataset description in markdown', required=True),
75
- 'created_at': fields.ISODateTime(
76
- description='The dataset creation date', required=True, readonly=True),
77
- 'last_modified': fields.ISODateTime(
78
- description='The dataset last modification date', required=True, readonly=True),
79
- 'deleted': fields.ISODateTime(description='The deletion date if deleted', readonly=True),
80
- 'archived': fields.ISODateTime(description='The archival date if archived'),
81
- 'featured': fields.Boolean(description='Is the dataset featured'),
82
- 'private': fields.Boolean(
83
- description='Is the dataset private to the owner or the organization'),
84
- 'tags': fields.List(fields.String),
85
- 'badges': fields.List(fields.Nested(badge_fields),
86
- description='The dataset badges',
87
- readonly=True),
88
- 'resources': fields.Raw(attribute=lambda o: {
89
- 'rel': 'subsection',
90
- 'href': url_for('apiv2.resources', dataset=o.id, page=1,
91
- page_size=DEFAULT_PAGE_SIZE, _external=True),
92
- 'type': 'GET',
93
- 'total': len(o.resources)
94
- }, description='Link to the dataset resources'),
95
- 'community_resources': fields.Raw(attribute=lambda o: {
96
- 'rel': 'subsection',
97
- 'href': url_for('api.community_resources', dataset=o.id, page=1,
98
- page_size=DEFAULT_PAGE_SIZE, _external=True),
99
- 'type': 'GET',
100
- 'total': len(o.community_resources)
101
- }, description='Link to the dataset community resources'),
102
- 'frequency': fields.String(
103
- description='The update frequency', required=True,
104
- enum=list(UPDATE_FREQUENCIES), default=DEFAULT_FREQUENCY),
105
- 'frequency_date': fields.ISODateTime(
106
- description=('Next expected update date, you will be notified '
107
- 'once that date is reached.')),
108
- 'harvest': fields.Nested(
109
- dataset_harvest_fields, readonly=True, allow_null=True,
110
- description='Dataset harvest metadata attributes',
111
- skip_none=True
112
- ),
113
- 'extras': fields.Raw(description='Extras attributes as key-value pairs'),
114
- 'metrics': fields.Raw(attribute=lambda o: o.get_metrics(), description='The dataset metrics'),
115
- 'organization': fields.Nested(
116
- org_ref_fields, allow_null=True,
117
- description='The producer organization'),
118
- 'owner': fields.Nested(
119
- user_ref_fields, allow_null=True, description='The user information'),
120
- 'temporal_coverage': fields.Nested(
121
- temporal_coverage_fields, allow_null=True,
122
- description='The temporal coverage'),
123
- 'spatial': fields.Nested(
124
- spatial_coverage_fields, allow_null=True,
125
- description='The spatial coverage'),
126
- 'license': fields.String(attribute='license.id',
127
- default=DEFAULT_LICENSE['id'],
128
- description='The dataset license'),
129
- 'uri': fields.UrlFor(
130
- 'api.dataset', lambda o: {'dataset': o},
131
- description='The dataset API URI', required=True),
132
- 'page': fields.UrlFor(
133
- 'datasets.show', lambda o: {'dataset': o},
134
- description='The dataset page URL', required=True, fallback_endpoint='api.dataset'),
135
- 'quality': fields.Raw(description='The dataset quality', readonly=True),
136
- 'last_update': fields.ISODateTime(
137
- description='The resources last modification date', required=True),
138
- 'internal': fields.Nested(
139
- dataset_internal_fields, readonly=True, description='Site internal and specific object\'s data'),
140
- 'contact_point': fields.Nested(contact_point_fields, allow_null=True, description='The dataset\'s contact point'),
141
- }, mask=DEFAULT_MASK_APIV2)
142
-
143
-
144
- resource_page_fields = apiv2.model('ResourcePage', {
145
- 'data': fields.List(
146
- fields.Nested(resource_fields, description='The dataset resources')
147
- ),
148
- 'next_page': fields.String(),
149
- 'previous_page': fields.String(),
150
- 'page': fields.Integer(),
151
- 'page_size': fields.Integer(),
152
- 'total': fields.Integer()
153
- })
88
+ "q", type=str, location="args", help="query string to search through resources titles"
89
+ )
90
+
91
+ common_doc = {"params": {"dataset": "The dataset ID or slug"}}
92
+
93
+
94
+ dataset_fields = apiv2.model(
95
+ "Dataset",
96
+ {
97
+ "id": fields.String(description="The dataset identifier", readonly=True),
98
+ "title": fields.String(description="The dataset title", required=True),
99
+ "acronym": fields.String(description="An optional dataset acronym"),
100
+ "slug": fields.String(description="The dataset permalink string", required=True),
101
+ "description": fields.Markdown(
102
+ description="The dataset description in markdown", required=True
103
+ ),
104
+ "created_at": fields.ISODateTime(
105
+ description="The dataset creation date", required=True, readonly=True
106
+ ),
107
+ "last_modified": fields.ISODateTime(
108
+ description="The dataset last modification date", required=True, readonly=True
109
+ ),
110
+ "deleted": fields.ISODateTime(description="The deletion date if deleted", readonly=True),
111
+ "archived": fields.ISODateTime(description="The archival date if archived"),
112
+ "featured": fields.Boolean(description="Is the dataset featured"),
113
+ "private": fields.Boolean(
114
+ description="Is the dataset private to the owner or the organization"
115
+ ),
116
+ "tags": fields.List(fields.String),
117
+ "badges": fields.List(
118
+ fields.Nested(badge_fields), description="The dataset badges", readonly=True
119
+ ),
120
+ "resources": fields.Raw(
121
+ attribute=lambda o: {
122
+ "rel": "subsection",
123
+ "href": url_for(
124
+ "apiv2.resources",
125
+ dataset=o.id,
126
+ page=1,
127
+ page_size=DEFAULT_PAGE_SIZE,
128
+ _external=True,
129
+ ),
130
+ "type": "GET",
131
+ "total": len(o.resources),
132
+ },
133
+ description="Link to the dataset resources",
134
+ ),
135
+ "community_resources": fields.Raw(
136
+ attribute=lambda o: {
137
+ "rel": "subsection",
138
+ "href": url_for(
139
+ "api.community_resources",
140
+ dataset=o.id,
141
+ page=1,
142
+ page_size=DEFAULT_PAGE_SIZE,
143
+ _external=True,
144
+ ),
145
+ "type": "GET",
146
+ "total": len(o.community_resources),
147
+ },
148
+ description="Link to the dataset community resources",
149
+ ),
150
+ "frequency": fields.String(
151
+ description="The update frequency",
152
+ required=True,
153
+ enum=list(UPDATE_FREQUENCIES),
154
+ default=DEFAULT_FREQUENCY,
155
+ ),
156
+ "frequency_date": fields.ISODateTime(
157
+ description=(
158
+ "Next expected update date, you will be notified " "once that date is reached."
159
+ )
160
+ ),
161
+ "harvest": fields.Nested(
162
+ dataset_harvest_fields,
163
+ readonly=True,
164
+ allow_null=True,
165
+ description="Dataset harvest metadata attributes",
166
+ skip_none=True,
167
+ ),
168
+ "extras": fields.Raw(description="Extras attributes as key-value pairs"),
169
+ "metrics": fields.Raw(
170
+ attribute=lambda o: o.get_metrics(), description="The dataset metrics"
171
+ ),
172
+ "organization": fields.Nested(
173
+ org_ref_fields, allow_null=True, description="The producer organization"
174
+ ),
175
+ "owner": fields.Nested(
176
+ user_ref_fields, allow_null=True, description="The user information"
177
+ ),
178
+ "temporal_coverage": fields.Nested(
179
+ temporal_coverage_fields, allow_null=True, description="The temporal coverage"
180
+ ),
181
+ "spatial": fields.Nested(
182
+ spatial_coverage_fields, allow_null=True, description="The spatial coverage"
183
+ ),
184
+ "license": fields.String(
185
+ attribute="license.id", default=DEFAULT_LICENSE["id"], description="The dataset license"
186
+ ),
187
+ "uri": fields.UrlFor(
188
+ "api.dataset",
189
+ lambda o: {"dataset": o},
190
+ description="The dataset API URI",
191
+ required=True,
192
+ ),
193
+ "page": fields.UrlFor(
194
+ "datasets.show",
195
+ lambda o: {"dataset": o},
196
+ description="The dataset page URL",
197
+ required=True,
198
+ fallback_endpoint="api.dataset",
199
+ ),
200
+ "quality": fields.Raw(description="The dataset quality", readonly=True),
201
+ "last_update": fields.ISODateTime(
202
+ description="The resources last modification date", required=True
203
+ ),
204
+ "internal": fields.Nested(
205
+ dataset_internal_fields,
206
+ readonly=True,
207
+ description="Site internal and specific object's data",
208
+ ),
209
+ "contact_point": fields.Nested(
210
+ contact_point_fields, allow_null=True, description="The dataset's contact point"
211
+ ),
212
+ },
213
+ mask=DEFAULT_MASK_APIV2,
214
+ )
215
+
216
+
217
+ resource_page_fields = apiv2.model(
218
+ "ResourcePage",
219
+ {
220
+ "data": fields.List(fields.Nested(resource_fields, description="The dataset resources")),
221
+ "next_page": fields.String(),
222
+ "previous_page": fields.String(),
223
+ "page": fields.Integer(),
224
+ "page_size": fields.Integer(),
225
+ "total": fields.Integer(),
226
+ },
227
+ )
154
228
 
155
229
  dataset_page_fields = apiv2.model(
156
- 'DatasetPage',
157
- fields.pager(dataset_fields),
158
- mask='data{{{0}}},*'.format(DEFAULT_MASK_APIV2)
230
+ "DatasetPage", fields.pager(dataset_fields), mask="data{{{0}}},*".format(DEFAULT_MASK_APIV2)
231
+ )
232
+
233
+ specific_resource_fields = apiv2.model(
234
+ "SpecificResource",
235
+ {
236
+ "resource": fields.Nested(resource_fields, description="The dataset resources"),
237
+ "dataset_id": fields.String(),
238
+ },
159
239
  )
160
240
 
161
- specific_resource_fields = apiv2.model('SpecificResource', {
162
- 'resource': fields.Nested(resource_fields, description='The dataset resources'),
163
- 'dataset_id': fields.String()
164
- })
165
-
166
- apiv2.inherit('Badge', badge_fields)
167
- apiv2.inherit('OrganizationReference', org_ref_fields)
168
- apiv2.inherit('UserReference', user_ref_fields)
169
- apiv2.inherit('MemberUserWithEmail', member_user_with_email_fields)
170
- apiv2.inherit('Resource', resource_fields)
171
- apiv2.inherit('SpatialCoverage', spatial_coverage_fields)
172
- apiv2.inherit('TemporalCoverage', temporal_coverage_fields)
173
- apiv2.inherit('GeoJSON', geojson)
174
- apiv2.inherit('Checksum', checksum_fields)
175
- apiv2.inherit('HarvestDatasetMetadata', dataset_harvest_fields)
176
- apiv2.inherit('HarvestResourceMetadata', resource_harvest_fields)
177
- apiv2.inherit('DatasetInternals', dataset_internal_fields)
178
- apiv2.inherit('ResourceInternals', resource_internal_fields)
179
- apiv2.inherit('ContactPoint', contact_point_fields)
180
- apiv2.inherit('Schema', schema_fields)
181
- apiv2.inherit('CatalogSchema', catalog_schema_fields)
182
-
183
-
184
- @ns.route('/search/', endpoint='dataset_search')
241
+ apiv2.inherit("Badge", badge_fields)
242
+ apiv2.inherit("OrganizationReference", org_ref_fields)
243
+ apiv2.inherit("UserReference", user_ref_fields)
244
+ apiv2.inherit("MemberUserWithEmail", member_user_with_email_fields)
245
+ apiv2.inherit("Resource", resource_fields)
246
+ apiv2.inherit("SpatialCoverage", spatial_coverage_fields)
247
+ apiv2.inherit("TemporalCoverage", temporal_coverage_fields)
248
+ apiv2.inherit("GeoJSON", geojson)
249
+ apiv2.inherit("Checksum", checksum_fields)
250
+ apiv2.inherit("HarvestDatasetMetadata", dataset_harvest_fields)
251
+ apiv2.inherit("HarvestResourceMetadata", resource_harvest_fields)
252
+ apiv2.inherit("DatasetInternals", dataset_internal_fields)
253
+ apiv2.inherit("ResourceInternals", resource_internal_fields)
254
+ apiv2.inherit("ContactPoint", contact_point_fields)
255
+ apiv2.inherit("Schema", schema_fields)
256
+ apiv2.inherit("CatalogSchema", catalog_schema_fields)
257
+
258
+
259
+ @ns.route("/search/", endpoint="dataset_search")
185
260
  class DatasetSearchAPI(API):
186
- '''Datasets collection search endpoint'''
187
- @apiv2.doc('search_datasets')
261
+ """Datasets collection search endpoint"""
262
+
263
+ @apiv2.doc("search_datasets")
188
264
  @apiv2.expect(search_parser)
189
265
  @apiv2.marshal_with(dataset_page_fields)
190
266
  def get(self):
191
- '''List or search all datasets'''
267
+ """List or search all datasets"""
192
268
  search_parser.parse_args()
193
269
  try:
194
270
  return search.query(Dataset, **multi_to_dict(request.args))
195
271
  except NotImplementedError:
196
- abort(501, 'Search endpoint not enabled')
272
+ abort(501, "Search endpoint not enabled")
197
273
  except RuntimeError:
198
- abort(500, 'Internal search service error')
274
+ abort(500, "Internal search service error")
199
275
 
200
276
 
201
- @ns.route('/<dataset:dataset>/', endpoint='dataset', doc=common_doc)
202
- @apiv2.response(404, 'Dataset not found')
203
- @apiv2.response(410, 'Dataset has been deleted')
277
+ @ns.route("/<dataset:dataset>/", endpoint="dataset", doc=common_doc)
278
+ @apiv2.response(404, "Dataset not found")
279
+ @apiv2.response(410, "Dataset has been deleted")
204
280
  class DatasetAPI(API):
205
- @apiv2.doc('get_dataset')
281
+ @apiv2.doc("get_dataset")
206
282
  @apiv2.marshal_with(dataset_fields)
207
283
  def get(self, dataset):
208
- '''Get a dataset given its identifier'''
284
+ """Get a dataset given its identifier"""
209
285
  if dataset.deleted and not DatasetEditPermission(dataset).can():
210
- apiv2.abort(410, 'Dataset has been deleted')
286
+ apiv2.abort(410, "Dataset has been deleted")
211
287
  return dataset
212
288
 
213
289
 
214
- @ns.route('/<dataset:dataset>/extras/', endpoint='dataset_extras',
215
- doc=common_doc)
216
- @apiv2.response(400, 'Wrong payload format, dict expected')
217
- @apiv2.response(400, 'Wrong payload format, list expected')
218
- @apiv2.response(404, 'Dataset not found')
219
- @apiv2.response(410, 'Dataset has been deleted')
290
+ @ns.route("/<dataset:dataset>/extras/", endpoint="dataset_extras", doc=common_doc)
291
+ @apiv2.response(400, "Wrong payload format, dict expected")
292
+ @apiv2.response(400, "Wrong payload format, list expected")
293
+ @apiv2.response(404, "Dataset not found")
294
+ @apiv2.response(410, "Dataset has been deleted")
220
295
  class DatasetExtrasAPI(API):
221
- @apiv2.doc('get_dataset_extras')
296
+ @apiv2.doc("get_dataset_extras")
222
297
  def get(self, dataset):
223
- '''Get a dataset extras given its identifier'''
298
+ """Get a dataset extras given its identifier"""
224
299
  if dataset.deleted and not DatasetEditPermission(dataset).can():
225
- apiv2.abort(410, 'Dataset has been deleted')
300
+ apiv2.abort(410, "Dataset has been deleted")
226
301
  return dataset.extras
227
302
 
228
303
  @apiv2.secure
229
- @apiv2.doc('update_dataset_extras')
304
+ @apiv2.doc("update_dataset_extras")
230
305
  def put(self, dataset):
231
- '''Update a given dataset extras'''
306
+ """Update a given dataset extras"""
232
307
  data = request.json
233
308
  if not isinstance(data, dict):
234
- apiv2.abort(400, 'Wrong payload format, dict expected')
309
+ apiv2.abort(400, "Wrong payload format, dict expected")
235
310
  if dataset.deleted:
236
- apiv2.abort(410, 'Dataset has been deleted')
311
+ apiv2.abort(410, "Dataset has been deleted")
237
312
  DatasetEditPermission(dataset).test()
238
313
  # first remove extras key associated to a None value in payload
239
314
  for key in [k for k in data if data[k] is None]:
@@ -242,52 +317,52 @@ class DatasetExtrasAPI(API):
242
317
  # then update the extras with the remaining payload
243
318
  dataset.extras.update(data)
244
319
  try:
245
- dataset.save(signal_kwargs={'ignores': ['post_save']})
320
+ dataset.save(signal_kwargs={"ignores": ["post_save"]})
246
321
  except mongoengine.errors.ValidationError as e:
247
322
  apiv2.abort(400, e.message)
248
323
  return dataset.extras
249
324
 
250
325
  @apiv2.secure
251
- @apiv2.doc('delete_dataset_extras')
326
+ @apiv2.doc("delete_dataset_extras")
252
327
  def delete(self, dataset):
253
- '''Delete a given dataset extras key on a given dataset'''
328
+ """Delete a given dataset extras key on a given dataset"""
254
329
  data = request.json
255
330
  if not isinstance(data, list):
256
- apiv2.abort(400, 'Wrong payload format, list expected')
331
+ apiv2.abort(400, "Wrong payload format, list expected")
257
332
  if dataset.deleted:
258
- apiv2.abort(410, 'Dataset has been deleted')
333
+ apiv2.abort(410, "Dataset has been deleted")
259
334
  DatasetEditPermission(dataset).test()
260
335
  for key in data:
261
336
  try:
262
337
  del dataset.extras[key]
263
338
  except KeyError:
264
339
  pass
265
- dataset.save(signal_kwargs={'ignores': ['post_save']})
340
+ dataset.save(signal_kwargs={"ignores": ["post_save"]})
266
341
  return dataset.extras, 204
267
342
 
268
343
 
269
- @ns.route('/<dataset:dataset>/resources/', endpoint='resources')
344
+ @ns.route("/<dataset:dataset>/resources/", endpoint="resources")
270
345
  class ResourcesAPI(API):
271
- @apiv2.doc('list_resources')
346
+ @apiv2.doc("list_resources")
272
347
  @apiv2.expect(resources_parser)
273
348
  @apiv2.marshal_with(resource_page_fields)
274
349
  def get(self, dataset):
275
- '''Get the given dataset resources, paginated.'''
350
+ """Get the given dataset resources, paginated."""
276
351
  args = resources_parser.parse_args()
277
- page = args['page']
278
- page_size = args['page_size']
279
- list_resources_url = url_for('apiv2.resources', dataset=dataset.id, _external=True)
352
+ page = args["page"]
353
+ page_size = args["page_size"]
354
+ list_resources_url = url_for("apiv2.resources", dataset=dataset.id, _external=True)
280
355
  next_page = f"{list_resources_url}?page={page + 1}&page_size={page_size}"
281
356
  previous_page = f"{list_resources_url}?page={page - 1}&page_size={page_size}"
282
357
  res = dataset.resources
283
358
 
284
- if args['type']:
285
- res = [elem for elem in res if elem['type'] == args['type']]
359
+ if args["type"]:
360
+ res = [elem for elem in res if elem["type"] == args["type"]]
286
361
  next_page += f"&type={args['type']}"
287
362
  previous_page += f"&type={args['type']}"
288
363
 
289
- if args['q']:
290
- res = [elem for elem in res if args['q'].lower() in elem['title'].lower()]
364
+ if args["q"]:
365
+ res = [elem for elem in res if args["q"].lower() in elem["title"].lower()]
291
366
  next_page += f"&q={args['q']}"
292
367
  previous_page += f"&q={args['q']}"
293
368
 
@@ -295,63 +370,64 @@ class ResourcesAPI(API):
295
370
  offset = page_size * (page - 1)
296
371
  else:
297
372
  offset = 0
298
- paginated_result = res[offset:(page_size + offset if page_size is not None else None)]
373
+ paginated_result = res[offset : (page_size + offset if page_size is not None else None)]
299
374
 
300
375
  return {
301
- 'data': paginated_result,
302
- 'next_page': next_page if page_size + offset < len(res) else None,
303
- 'page': page,
304
- 'page_size': page_size,
305
- 'previous_page': previous_page if page > 1 else None,
306
- 'total': len(res),
376
+ "data": paginated_result,
377
+ "next_page": next_page if page_size + offset < len(res) else None,
378
+ "page": page,
379
+ "page_size": page_size,
380
+ "previous_page": previous_page if page > 1 else None,
381
+ "total": len(res),
307
382
  }
308
383
 
309
384
 
310
- @ns.route('/resources/<uuid:rid>/', endpoint='resource')
385
+ @ns.route("/resources/<uuid:rid>/", endpoint="resource")
311
386
  class ResourceAPI(API):
312
- @apiv2.doc('get_resource')
387
+ @apiv2.doc("get_resource")
313
388
  def get(self, rid):
314
389
  dataset = Dataset.objects(resources__id=rid).first()
315
390
  if dataset:
316
- resource = get_by(dataset.resources, 'id', rid)
391
+ resource = get_by(dataset.resources, "id", rid)
317
392
  else:
318
393
  resource = CommunityResource.objects(id=rid).first()
319
394
  if not resource:
320
- apiv2.abort(404, 'Resource does not exist')
395
+ apiv2.abort(404, "Resource does not exist")
321
396
 
322
397
  # Manually marshalling to make sure resource.dataset is in the scope.
323
398
  # See discussions in https://github.com/opendatateam/udata/pull/2732/files
324
- return marshal({
325
- 'resource': resource,
326
- 'dataset_id': dataset.id if dataset else None
327
- }, specific_resource_fields)
328
-
329
-
330
- @ns.route('/<dataset:dataset>/resources/<uuid:rid>/extras/', endpoint='resource_extras',
331
- doc=common_doc)
332
- @apiv2.param('rid', 'The resource unique identifier')
333
- @apiv2.response(400, 'Wrong payload format, dict expected')
334
- @apiv2.response(400, 'Wrong payload format, list expected')
335
- @apiv2.response(404, 'Key not found in existing extras')
336
- @apiv2.response(410, 'Dataset has been deleted')
399
+ return marshal(
400
+ {"resource": resource, "dataset_id": dataset.id if dataset else None},
401
+ specific_resource_fields,
402
+ )
403
+
404
+
405
+ @ns.route(
406
+ "/<dataset:dataset>/resources/<uuid:rid>/extras/", endpoint="resource_extras", doc=common_doc
407
+ )
408
+ @apiv2.param("rid", "The resource unique identifier")
409
+ @apiv2.response(400, "Wrong payload format, dict expected")
410
+ @apiv2.response(400, "Wrong payload format, list expected")
411
+ @apiv2.response(404, "Key not found in existing extras")
412
+ @apiv2.response(410, "Dataset has been deleted")
337
413
  class ResourceExtrasAPI(ResourceMixin, API):
338
- @apiv2.doc('get_resource_extras')
414
+ @apiv2.doc("get_resource_extras")
339
415
  def get(self, dataset, rid):
340
- '''Get a resource extras given its identifier'''
416
+ """Get a resource extras given its identifier"""
341
417
  if dataset.deleted and not DatasetEditPermission(dataset).can():
342
- apiv2.abort(410, 'Dataset has been deleted')
418
+ apiv2.abort(410, "Dataset has been deleted")
343
419
  resource = self.get_resource_or_404(dataset, rid)
344
420
  return resource.extras
345
421
 
346
422
  @apiv2.secure
347
- @apiv2.doc('update_resource_extras')
423
+ @apiv2.doc("update_resource_extras")
348
424
  def put(self, dataset, rid):
349
- '''Update a given resource extras on a given dataset'''
425
+ """Update a given resource extras on a given dataset"""
350
426
  data = request.json
351
427
  if not isinstance(data, dict):
352
- apiv2.abort(400, 'Wrong payload format, dict expected')
428
+ apiv2.abort(400, "Wrong payload format, dict expected")
353
429
  if dataset.deleted:
354
- apiv2.abort(410, 'Dataset has been deleted')
430
+ apiv2.abort(410, "Dataset has been deleted")
355
431
  ResourceEditPermission(dataset).test()
356
432
  resource = self.get_resource_or_404(dataset, rid)
357
433
  # first remove extras key associated to a None value in payload
@@ -360,24 +436,24 @@ class ResourceExtrasAPI(ResourceMixin, API):
360
436
  data.pop(key)
361
437
  # then update the extras with the remaining payload
362
438
  resource.extras.update(data)
363
- resource.save(signal_kwargs={'ignores': ['post_save']})
439
+ resource.save(signal_kwargs={"ignores": ["post_save"]})
364
440
  return resource.extras
365
441
 
366
442
  @apiv2.secure
367
- @apiv2.doc('delete_resource_extras')
443
+ @apiv2.doc("delete_resource_extras")
368
444
  def delete(self, dataset, rid):
369
- '''Delete a given resource extras key on a given dataset'''
445
+ """Delete a given resource extras key on a given dataset"""
370
446
  data = request.json
371
447
  if not isinstance(data, list):
372
- apiv2.abort(400, 'Wrong payload format, list expected')
448
+ apiv2.abort(400, "Wrong payload format, list expected")
373
449
  if dataset.deleted:
374
- apiv2.abort(410, 'Dataset has been deleted')
450
+ apiv2.abort(410, "Dataset has been deleted")
375
451
  ResourceEditPermission(dataset).test()
376
452
  resource = self.get_resource_or_404(dataset, rid)
377
453
  try:
378
454
  for key in data:
379
455
  del resource.extras[key]
380
456
  except KeyError:
381
- apiv2.abort(404, 'Key not found in existing extras')
382
- resource.save(signal_kwargs={'ignores': ['post_save']})
457
+ apiv2.abort(404, "Key not found in existing extras")
458
+ resource.save(signal_kwargs={"ignores": ["post_save"]})
383
459
  return resource.extras, 204