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
udata/api/fields.py CHANGED
@@ -1,5 +1,5 @@
1
- import logging
2
1
  import datetime
2
+ import logging
3
3
 
4
4
  import pytz
5
5
  from dateutil.parser import parse
@@ -13,51 +13,57 @@ log = logging.getLogger(__name__)
13
13
 
14
14
 
15
15
  class ISODateTime(String):
16
- __schema_format__ = 'date-time'
16
+ __schema_format__ = "date-time"
17
17
 
18
18
  def format(self, value):
19
19
  if isinstance(value, str):
20
20
  value = parse(value)
21
- if isinstance(value, datetime.date) and not isinstance(value, datetime.datetime) or value.tzinfo:
21
+ if (
22
+ isinstance(value, datetime.date)
23
+ and not isinstance(value, datetime.datetime)
24
+ or value.tzinfo
25
+ ):
22
26
  return value.isoformat()
23
27
  return pytz.utc.localize(value).isoformat()
24
28
 
25
29
 
26
30
  class Markdown(String):
27
- __schema_format__ = 'markdown'
31
+ __schema_format__ = "markdown"
28
32
 
29
33
 
30
34
  class UrlFor(String):
31
35
  def __init__(self, endpoint, mapper=None, **kwargs):
32
36
  super(UrlFor, self).__init__(**kwargs)
33
37
  self.endpoint = endpoint
34
- self.fallback_endpoint = kwargs.pop('fallback_endpoint', None)
38
+ self.fallback_endpoint = kwargs.pop("fallback_endpoint", None)
35
39
  self.mapper = mapper or self.default_mapper
36
40
 
37
41
  def default_mapper(self, obj):
38
- return {'id': str(obj.id)}
42
+ return {"id": str(obj.id)}
39
43
 
40
44
  def output(self, key, obj, **kwargs):
41
- return endpoint_for(self.endpoint, self.fallback_endpoint, _external=True, **self.mapper(obj))
45
+ return endpoint_for(
46
+ self.endpoint, self.fallback_endpoint, _external=True, **self.mapper(obj)
47
+ )
42
48
 
43
49
 
44
50
  class NextPageUrl(String):
45
51
  def output(self, key, obj, **kwargs):
46
- if not getattr(obj, 'has_next', None):
52
+ if not getattr(obj, "has_next", None):
47
53
  return None
48
54
  args = multi_to_dict(request.args)
49
55
  args.update(request.view_args)
50
- args['page'] = obj.page + 1
56
+ args["page"] = obj.page + 1
51
57
  return url_for(request.endpoint, _external=True, **args)
52
58
 
53
59
 
54
60
  class PreviousPageUrl(String):
55
61
  def output(self, key, obj, **kwargs):
56
- if not getattr(obj, 'has_prev', None):
62
+ if not getattr(obj, "has_prev", None):
57
63
  return None
58
64
  args = multi_to_dict(request.args)
59
65
  args.update(request.view_args)
60
- args['page'] = obj.page - 1
66
+ args["page"] = obj.page - 1
61
67
  return url_for(request.endpoint, _external=True, **args)
62
68
 
63
69
 
@@ -81,15 +87,11 @@ class ImageField(String):
81
87
 
82
88
  def pager(page_fields):
83
89
  pager_fields = {
84
- 'data': List(Nested(page_fields), attribute='objects',
85
- description='The page data'),
86
- 'page': Integer(description='The current page', required=True, min=1),
87
- 'page_size': Integer(description='The page size used for pagination',
88
- required=True, min=0),
89
- 'total': Integer(description='The total paginated items',
90
- required=True, min=0),
91
- 'next_page': NextPageUrl(description='The next page URL if exists'),
92
- 'previous_page': PreviousPageUrl(
93
- description='The previous page URL if exists')
90
+ "data": List(Nested(page_fields), attribute="objects", description="The page data"),
91
+ "page": Integer(description="The current page", required=True, min=1),
92
+ "page_size": Integer(description="The page size used for pagination", required=True, min=0),
93
+ "total": Integer(description="The total paginated items", required=True, min=0),
94
+ "next_page": NextPageUrl(description="The next page URL if exists"),
95
+ "previous_page": PreviousPageUrl(description="The previous page URL if exists"),
94
96
  }
95
97
  return pager_fields
udata/api/oauth2.py CHANGED
@@ -1,4 +1,4 @@
1
- '''
1
+ """
2
2
  OAuth 2 serveur implementation based on Authlib.
3
3
 
4
4
  See the documentatiosn here:
@@ -12,22 +12,23 @@ Authlib provides SQLAlchemny mixins which help understanding:
12
12
 
13
13
  As well as a sample application:
14
14
  - https://github.com/authlib/example-oauth2-server
15
- '''
16
- import fnmatch
17
-
18
- from bson import ObjectId
15
+ """
19
16
 
17
+ import fnmatch
20
18
  from datetime import datetime, timedelta
21
19
 
22
- from authlib.integrations.flask_oauth2.errors import _HTTPException as AuthlibFlaskException
23
20
  from authlib.integrations.flask_oauth2 import AuthorizationServer, ResourceProtector
24
- from authlib.oauth2.rfc6749 import grants, ClientMixin
21
+ from authlib.integrations.flask_oauth2.errors import (
22
+ _HTTPException as AuthlibFlaskException,
23
+ )
24
+ from authlib.oauth2 import OAuth2Error
25
+ from authlib.oauth2.rfc6749 import ClientMixin, grants
26
+ from authlib.oauth2.rfc6749.util import list_to_scope, scope_to_list
25
27
  from authlib.oauth2.rfc6750 import BearerTokenValidator
26
28
  from authlib.oauth2.rfc7009 import RevocationEndpoint
27
29
  from authlib.oauth2.rfc7636 import CodeChallenge
28
- from authlib.oauth2.rfc6749.util import scope_to_list, list_to_scope
29
- from authlib.oauth2 import OAuth2Error
30
- from flask import request, render_template, current_app
30
+ from bson import ObjectId
31
+ from flask import current_app, render_template, request
31
32
  from flask_security.utils import verify_password
32
33
  from werkzeug.exceptions import Unauthorized
33
34
  from werkzeug.security import gen_salt
@@ -35,12 +36,12 @@ from werkzeug.security import gen_salt
35
36
  from udata.app import csrf
36
37
  from udata.auth import current_user, login_required, login_user
37
38
  from udata.core.organization.models import Organization
38
- from udata.i18n import I18nBlueprint, lazy_gettext as _
39
+ from udata.core.storages import default_image_basename, images
40
+ from udata.i18n import I18nBlueprint
41
+ from udata.i18n import lazy_gettext as _
39
42
  from udata.mongo import db
40
- from udata.core.storages import images, default_image_basename
41
-
42
43
 
43
- blueprint = I18nBlueprint('oauth', __name__, url_prefix='/oauth')
44
+ blueprint = I18nBlueprint("oauth", __name__, url_prefix="/oauth")
44
45
  oauth = AuthorizationServer()
45
46
  require_oauth = ResourceProtector()
46
47
 
@@ -51,13 +52,10 @@ REFRESH_EXPIRATION = 30 # days
51
52
  EPOCH = datetime.fromtimestamp(0)
52
53
 
53
54
  TOKEN_TYPES = {
54
- 'Bearer': _('Bearer Token'),
55
+ "Bearer": _("Bearer Token"),
55
56
  }
56
57
 
57
- SCOPES = {
58
- 'default': _('Default scope'),
59
- 'admin': _('System administrator rights')
60
- }
58
+ SCOPES = {"default": _("Default scope"), "admin": _("System administrator rights")}
61
59
 
62
60
 
63
61
  class OAuth2Client(ClientMixin, db.Datetimed, db.Document):
@@ -66,22 +64,19 @@ class OAuth2Client(ClientMixin, db.Datetimed, db.Document):
66
64
  name = db.StringField(required=True)
67
65
  description = db.StringField()
68
66
 
69
- owner = db.ReferenceField('User')
67
+ owner = db.ReferenceField("User")
70
68
  organization = db.ReferenceField(Organization, reverse_delete_rule=db.NULLIFY)
71
- image = db.ImageField(fs=images, basename=default_image_basename,
72
- thumbnails=[150, 25])
69
+ image = db.ImageField(fs=images, basename=default_image_basename, thumbnails=[150, 25])
73
70
 
74
71
  redirect_uris = db.ListField(db.StringField())
75
- scope = db.StringField(default='default')
72
+ scope = db.StringField(default="default")
76
73
  grant_types = db.ListField(db.StringField())
77
74
  response_types = db.ListField(db.StringField())
78
75
 
79
76
  confidential = db.BooleanField(default=False)
80
77
  internal = db.BooleanField(default=False)
81
78
 
82
- meta = {
83
- 'collection': 'oauth2_client'
84
- }
79
+ meta = {"collection": "oauth2_client"}
85
80
 
86
81
  def get_client_id(self):
87
82
  return str(self.id)
@@ -103,15 +98,13 @@ class OAuth2Client(ClientMixin, db.Datetimed, db.Document):
103
98
 
104
99
  def get_allowed_scope(self, scope):
105
100
  if not scope:
106
- return ''
101
+ return ""
107
102
  allowed = set(scope_to_list(self.scope))
108
103
  return list_to_scope([s for s in scope.split() if s in allowed])
109
104
 
110
105
  def check_redirect_uri(self, redirect_uri):
111
106
  if current_app.config.get("OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI"):
112
- return any(
113
- fnmatch.fnmatch(redirect_uri, pattern) for pattern in self.redirect_uris
114
- )
107
+ return any(fnmatch.fnmatch(redirect_uri, pattern) for pattern in self.redirect_uris)
115
108
  else:
116
109
  return redirect_uri in self.redirect_uris
117
110
 
@@ -120,8 +113,8 @@ class OAuth2Client(ClientMixin, db.Datetimed, db.Document):
120
113
 
121
114
  def check_token_endpoint_auth_method(self, method):
122
115
  if not self.has_client_secret():
123
- return method == 'none'
124
- return method in ('client_secret_post', 'client_secret_basic')
116
+ return method == "none"
117
+ return method in ("client_secret_post", "client_secret_basic")
125
118
 
126
119
  def check_response_type(self, response_type):
127
120
  return True
@@ -138,25 +131,23 @@ class OAuth2Client(ClientMixin, db.Datetimed, db.Document):
138
131
 
139
132
 
140
133
  class OAuth2Token(db.Document):
141
- client = db.ReferenceField('OAuth2Client', required=True)
142
- user = db.ReferenceField('User')
134
+ client = db.ReferenceField("OAuth2Client", required=True)
135
+ user = db.ReferenceField("User")
143
136
 
144
137
  # currently only bearer is supported
145
- token_type = db.StringField(choices=list(TOKEN_TYPES), default='Bearer')
138
+ token_type = db.StringField(choices=list(TOKEN_TYPES), default="Bearer")
146
139
 
147
140
  access_token = db.StringField(unique=True)
148
141
  refresh_token = db.StringField(unique=True, sparse=True)
149
142
  created_at = db.DateTimeField(default=datetime.utcnow, required=True)
150
143
  expires_in = db.IntField(required=True, default=TOKEN_EXPIRATION)
151
- scope = db.StringField(default='')
144
+ scope = db.StringField(default="")
152
145
  revoked = db.BooleanField(default=False)
153
146
 
154
- meta = {
155
- 'collection': 'oauth2_token'
156
- }
147
+ meta = {"collection": "oauth2_token"}
157
148
 
158
149
  def __str__(self):
159
- return '<OAuth2Token({0.client.name})>'.format(self)
150
+ return "<OAuth2Token({0.client.name})>".format(self)
160
151
 
161
152
  def get_scope(self):
162
153
  return self.scope
@@ -179,24 +170,22 @@ class OAuth2Token(db.Document):
179
170
 
180
171
 
181
172
  class OAuth2Code(db.Document):
182
- user = db.ReferenceField('User', required=True)
183
- client = db.ReferenceField('OAuth2Client', required=True)
173
+ user = db.ReferenceField("User", required=True)
174
+ client = db.ReferenceField("OAuth2Client", required=True)
184
175
 
185
176
  code = db.StringField(required=True)
186
177
 
187
178
  redirect_uri = db.StringField()
188
179
  expires = db.DateTimeField()
189
180
 
190
- scope = db.StringField(default='')
181
+ scope = db.StringField(default="")
191
182
  code_challenge = db.StringField()
192
183
  code_challenge_method = db.StringField()
193
184
 
194
- meta = {
195
- 'collection': 'oauth2_code'
196
- }
185
+ meta = {"collection": "oauth2_code"}
197
186
 
198
187
  def __str__(self):
199
- return '<OAuth2Code({0.client.name}, {0.user.fullname})>'.format(self)
188
+ return "<OAuth2Code({0.client.name}, {0.user.fullname})>".format(self)
200
189
 
201
190
  def is_expired(self):
202
191
  return self.expires < datetime.utcnow()
@@ -209,14 +198,11 @@ class OAuth2Code(db.Document):
209
198
 
210
199
 
211
200
  class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
212
- TOKEN_ENDPOINT_AUTH_METHODS = [
213
- 'client_secret_basic',
214
- 'client_secret_post'
215
- ]
201
+ TOKEN_ENDPOINT_AUTH_METHODS = ["client_secret_basic", "client_secret_post"]
216
202
 
217
203
  def save_authorization_code(self, code, request):
218
- code_challenge = request.data.get('code_challenge')
219
- code_challenge_method = request.data.get('code_challenge_method')
204
+ code_challenge = request.data.get("code_challenge")
205
+ code_challenge_method = request.data.get("code_challenge_method")
220
206
  expires = datetime.utcnow() + timedelta(seconds=GRANT_EXPIRATION)
221
207
  auth_code = OAuth2Code.objects.create(
222
208
  code=code,
@@ -268,9 +254,9 @@ class RefreshTokenGrant(grants.RefreshTokenGrant):
268
254
  class RevokeToken(RevocationEndpoint):
269
255
  def query_token(self, token, token_type_hint, client):
270
256
  qs = OAuth2Token.objects(client=client)
271
- if token_type_hint == 'access_token':
257
+ if token_type_hint == "access_token":
272
258
  return qs.filter(access_token=token).first()
273
- elif token_type_hint == 'refresh_token':
259
+ elif token_type_hint == "refresh_token":
274
260
  return qs.filter(refresh_token=token).first()
275
261
  else:
276
262
  qs = qs(db.Q(access_token=token) | db.Q(refresh_token=token))
@@ -292,22 +278,22 @@ class BearerToken(BearerTokenValidator):
292
278
  return token.revoked
293
279
 
294
280
 
295
- @blueprint.route('/token', methods=['POST'], localize=False, endpoint='token')
281
+ @blueprint.route("/token", methods=["POST"], localize=False, endpoint="token")
296
282
  @csrf.exempt
297
283
  def access_token():
298
284
  return oauth.create_token_response()
299
285
 
300
286
 
301
- @blueprint.route('/revoke', methods=['POST'], localize=False)
287
+ @blueprint.route("/revoke", methods=["POST"], localize=False)
302
288
  @csrf.exempt
303
289
  def revoke_token():
304
290
  return oauth.create_endpoint_response(RevokeToken.ENDPOINT_NAME)
305
291
 
306
292
 
307
- @blueprint.route('/authorize', methods=['GET', 'POST'])
293
+ @blueprint.route("/authorize", methods=["GET", "POST"])
308
294
  @login_required
309
295
  def authorize(*args, **kwargs):
310
- if request.method == 'GET':
296
+ if request.method == "GET":
311
297
  try:
312
298
  grant = oauth.validate_consent_request(end_user=current_user)
313
299
  except OAuth2Error as error:
@@ -315,10 +301,10 @@ def authorize(*args, **kwargs):
315
301
  # Bypass authorization screen for internal clients
316
302
  if grant.client.internal:
317
303
  return oauth.create_authorization_response(grant_user=current_user)
318
- return render_template('api/oauth_authorize.html', grant=grant)
319
- elif request.method == 'POST':
320
- accept = 'accept' in request.form
321
- decline = 'decline' in request.form
304
+ return render_template("api/oauth_authorize.html", grant=grant)
305
+ elif request.method == "POST":
306
+ accept = "accept" in request.form
307
+ decline = "decline" in request.form
322
308
  if accept and not decline:
323
309
  grant_user = current_user
324
310
  else:
@@ -326,30 +312,25 @@ def authorize(*args, **kwargs):
326
312
  return oauth.create_authorization_response(grant_user=grant_user)
327
313
 
328
314
 
329
- @blueprint.route('/error')
315
+ @blueprint.route("/error")
330
316
  def oauth_error():
331
- return render_template('api/oauth_error.html')
317
+ return render_template("api/oauth_error.html")
332
318
 
333
319
 
334
320
  def query_client(client_id):
335
- '''Fetch client by ID'''
321
+ """Fetch client by ID"""
336
322
  return OAuth2Client.objects(id=ObjectId(client_id)).first()
337
323
 
338
324
 
339
325
  def save_token(token, request):
340
- scope = token.pop('scope', '')
341
- if request.grant_type == 'refresh_token':
326
+ scope = token.pop("scope", "")
327
+ if request.grant_type == "refresh_token":
342
328
  credential = request.credential
343
329
  credential.update(scope=scope, **token)
344
330
  else:
345
331
  client = request.client
346
332
  user = request.user or client.owner
347
- OAuth2Token.objects.create(
348
- client=client,
349
- user=user.id,
350
- scope=scope,
351
- **token
352
- )
333
+ OAuth2Token.objects.create(client=client, user=user.id, scope=scope, **token)
353
334
 
354
335
 
355
336
  def check_credentials():
udata/api/parsers.py CHANGED
@@ -9,28 +9,28 @@ class ModelApiParser:
9
9
  def __init__(self, paginate=True):
10
10
  self.parser = api.parser()
11
11
  # q parameter
12
- self.parser.add_argument('q', type=str, location='args',
13
- help='The search query')
12
+ self.parser.add_argument("q", type=str, location="args", help="The search query")
14
13
  # Sort arguments
15
14
  keys = list(self.sorts)
16
- choices = keys + ['-' + k for k in keys]
17
- help_msg = 'The field (and direction) on which sorting apply'
18
- self.parser.add_argument('sort', type=str, location='args',
19
- choices=choices, help=help_msg)
15
+ choices = keys + ["-" + k for k in keys]
16
+ help_msg = "The field (and direction) on which sorting apply"
17
+ self.parser.add_argument("sort", type=str, location="args", choices=choices, help=help_msg)
20
18
  if paginate:
21
- self.parser.add_argument('page', type=int, location='args',
22
- default=1, help='The page to display')
23
- self.parser.add_argument('page_size', type=int, location='args',
24
- default=20, help='The page size')
19
+ self.parser.add_argument(
20
+ "page", type=int, location="args", default=1, help="The page to display"
21
+ )
22
+ self.parser.add_argument(
23
+ "page_size", type=int, location="args", default=20, help="The page size"
24
+ )
25
25
 
26
26
  def parse(self):
27
27
  args = self.parser.parse_args()
28
- if args['sort']:
29
- if args['sort'].startswith('-'):
28
+ if args["sort"]:
29
+ if args["sort"].startswith("-"):
30
30
  # Keyerror because of the '-' character in front of the argument.
31
31
  # It is removed to find the value in dict and added back.
32
- arg_sort = args['sort'][1:]
33
- args['sort'] = '-' + self.sorts[arg_sort]
32
+ arg_sort = args["sort"][1:]
33
+ args["sort"] = "-" + self.sorts[arg_sort]
34
34
  else:
35
- args['sort'] = self.sorts[args['sort']]
35
+ args["sort"] = self.sorts[args["sort"]]
36
36
  return args
udata/api/signals.py CHANGED
@@ -3,4 +3,4 @@ from blinker import Namespace
3
3
  namespace = Namespace()
4
4
 
5
5
  #: Trigerred when an HTTP request is issued against the API
6
- on_api_call = namespace.signal('on-api-call')
6
+ on_api_call = namespace.signal("on-api-call")