udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30382__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 (425) 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 +135 -124
  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 +56 -54
  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/db/tasks.py +2 -1
  199. udata/entrypoints.py +35 -31
  200. udata/errors.py +2 -1
  201. udata/event/values.py +6 -6
  202. udata/factories.py +2 -2
  203. udata/features/identicon/api.py +5 -6
  204. udata/features/identicon/backends.py +48 -55
  205. udata/features/identicon/tests/test_backends.py +4 -5
  206. udata/features/notifications/__init__.py +0 -1
  207. udata/features/notifications/actions.py +9 -9
  208. udata/features/notifications/api.py +17 -13
  209. udata/features/territories/__init__.py +12 -10
  210. udata/features/territories/api.py +14 -15
  211. udata/features/territories/models.py +23 -28
  212. udata/features/transfer/actions.py +8 -11
  213. udata/features/transfer/api.py +84 -77
  214. udata/features/transfer/factories.py +2 -1
  215. udata/features/transfer/models.py +11 -12
  216. udata/features/transfer/notifications.py +19 -15
  217. udata/features/transfer/permissions.py +5 -5
  218. udata/forms/__init__.py +5 -2
  219. udata/forms/fields.py +164 -172
  220. udata/forms/validators.py +19 -22
  221. udata/forms/widgets.py +9 -13
  222. udata/frontend/__init__.py +31 -26
  223. udata/frontend/csv.py +68 -58
  224. udata/frontend/markdown.py +40 -44
  225. udata/harvest/actions.py +89 -77
  226. udata/harvest/api.py +294 -238
  227. udata/harvest/backends/__init__.py +4 -4
  228. udata/harvest/backends/base.py +128 -111
  229. udata/harvest/backends/dcat.py +80 -66
  230. udata/harvest/commands.py +56 -60
  231. udata/harvest/csv.py +8 -8
  232. udata/harvest/exceptions.py +6 -3
  233. udata/harvest/filters.py +24 -23
  234. udata/harvest/forms.py +27 -28
  235. udata/harvest/models.py +88 -80
  236. udata/harvest/notifications.py +15 -10
  237. udata/harvest/signals.py +13 -13
  238. udata/harvest/tasks.py +11 -10
  239. udata/harvest/tests/factories.py +23 -24
  240. udata/harvest/tests/test_actions.py +136 -166
  241. udata/harvest/tests/test_api.py +220 -214
  242. udata/harvest/tests/test_base_backend.py +117 -112
  243. udata/harvest/tests/test_dcat_backend.py +380 -308
  244. udata/harvest/tests/test_filters.py +33 -22
  245. udata/harvest/tests/test_models.py +11 -14
  246. udata/harvest/tests/test_notifications.py +6 -7
  247. udata/harvest/tests/test_tasks.py +7 -6
  248. udata/i18n.py +237 -78
  249. udata/linkchecker/backends.py +5 -11
  250. udata/linkchecker/checker.py +23 -22
  251. udata/linkchecker/commands.py +4 -6
  252. udata/linkchecker/models.py +6 -6
  253. udata/linkchecker/tasks.py +18 -20
  254. udata/mail.py +21 -21
  255. udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
  256. udata/migrations/2020-08-24-add-fs-filename.py +9 -8
  257. udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
  258. udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
  259. udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
  260. udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
  261. udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
  262. udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
  263. udata/migrations/2021-08-17-follow-integrity.py +5 -4
  264. udata/migrations/2021-08-17-harvest-integrity.py +13 -12
  265. udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
  266. udata/migrations/2021-08-17-transfer-integrity.py +5 -4
  267. udata/migrations/2021-08-17-users-integrity.py +9 -8
  268. udata/migrations/2021-12-14-reuse-topics.py +7 -6
  269. udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
  270. udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
  271. udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
  272. udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
  273. udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
  274. udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
  275. udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
  276. udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
  277. udata/migrations/__init__.py +123 -105
  278. udata/models/__init__.py +4 -4
  279. udata/mongo/__init__.py +13 -11
  280. udata/mongo/badges_field.py +3 -2
  281. udata/mongo/datetime_fields.py +13 -12
  282. udata/mongo/document.py +17 -16
  283. udata/mongo/engine.py +15 -16
  284. udata/mongo/errors.py +2 -1
  285. udata/mongo/extras_fields.py +30 -20
  286. udata/mongo/queryset.py +12 -12
  287. udata/mongo/slug_fields.py +38 -28
  288. udata/mongo/taglist_field.py +1 -2
  289. udata/mongo/url_field.py +5 -5
  290. udata/mongo/uuid_fields.py +4 -3
  291. udata/notifications/__init__.py +1 -1
  292. udata/notifications/mattermost.py +10 -9
  293. udata/rdf.py +167 -188
  294. udata/routing.py +40 -45
  295. udata/search/__init__.py +18 -19
  296. udata/search/adapter.py +17 -16
  297. udata/search/commands.py +44 -51
  298. udata/search/fields.py +13 -20
  299. udata/search/query.py +23 -18
  300. udata/search/result.py +9 -10
  301. udata/sentry.py +21 -19
  302. udata/settings.py +262 -198
  303. udata/sitemap.py +8 -6
  304. udata/static/chunks/{11.e9b9ca1f3e03d4020377.js → 11.52e531c19f8de80c00cf.js} +3 -3
  305. udata/static/chunks/{11.e9b9ca1f3e03d4020377.js.map → 11.52e531c19f8de80c00cf.js.map} +1 -1
  306. udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js → 13.c3343a7f1070061c0e10.js} +2 -2
  307. udata/static/chunks/{13.038c0d9aa0dfa0181c4b.js.map → 13.c3343a7f1070061c0e10.js.map} +1 -1
  308. udata/static/chunks/{16.0baa2b64a74a2dcde25c.js → 16.8fa42440ad75ca172e6d.js} +2 -2
  309. udata/static/chunks/{16.0baa2b64a74a2dcde25c.js.map → 16.8fa42440ad75ca172e6d.js.map} +1 -1
  310. udata/static/chunks/{19.350a9f150b074b4ecefa.js → 19.9c6c8412729cd6d59cfa.js} +3 -3
  311. udata/static/chunks/{19.350a9f150b074b4ecefa.js.map → 19.9c6c8412729cd6d59cfa.js.map} +1 -1
  312. udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js → 5.71d15c2e4f21feee2a9a.js} +3 -3
  313. udata/static/chunks/{5.6ebbce2b9b3e696d3da5.js.map → 5.71d15c2e4f21feee2a9a.js.map} +1 -1
  314. udata/static/chunks/{6.d8a5f7b017bcbd083641.js → 6.9139dc098b8ea640b890.js} +3 -3
  315. udata/static/chunks/{6.d8a5f7b017bcbd083641.js.map → 6.9139dc098b8ea640b890.js.map} +1 -1
  316. udata/static/common.js +1 -1
  317. udata/static/common.js.map +1 -1
  318. udata/storage/s3.py +20 -13
  319. udata/tags.py +4 -5
  320. udata/tasks.py +43 -42
  321. udata/tests/__init__.py +9 -6
  322. udata/tests/api/__init__.py +5 -6
  323. udata/tests/api/test_auth_api.py +395 -321
  324. udata/tests/api/test_base_api.py +31 -33
  325. udata/tests/api/test_contact_points.py +7 -9
  326. udata/tests/api/test_dataservices_api.py +211 -158
  327. udata/tests/api/test_datasets_api.py +823 -812
  328. udata/tests/api/test_follow_api.py +13 -15
  329. udata/tests/api/test_me_api.py +95 -112
  330. udata/tests/api/test_organizations_api.py +301 -339
  331. udata/tests/api/test_reports_api.py +35 -25
  332. udata/tests/api/test_reuses_api.py +134 -139
  333. udata/tests/api/test_swagger.py +5 -5
  334. udata/tests/api/test_tags_api.py +18 -25
  335. udata/tests/api/test_topics_api.py +94 -94
  336. udata/tests/api/test_transfer_api.py +53 -48
  337. udata/tests/api/test_user_api.py +128 -141
  338. udata/tests/apiv2/test_datasets.py +290 -198
  339. udata/tests/apiv2/test_me_api.py +10 -11
  340. udata/tests/apiv2/test_organizations.py +56 -74
  341. udata/tests/apiv2/test_swagger.py +5 -5
  342. udata/tests/apiv2/test_topics.py +69 -87
  343. udata/tests/cli/test_cli_base.py +8 -8
  344. udata/tests/cli/test_db_cli.py +21 -19
  345. udata/tests/dataservice/test_dataservice_tasks.py +8 -12
  346. udata/tests/dataset/test_csv_adapter.py +44 -35
  347. udata/tests/dataset/test_dataset_actions.py +2 -3
  348. udata/tests/dataset/test_dataset_commands.py +7 -8
  349. udata/tests/dataset/test_dataset_events.py +36 -29
  350. udata/tests/dataset/test_dataset_model.py +224 -217
  351. udata/tests/dataset/test_dataset_rdf.py +142 -131
  352. udata/tests/dataset/test_dataset_tasks.py +15 -15
  353. udata/tests/dataset/test_resource_preview.py +10 -13
  354. udata/tests/features/territories/__init__.py +9 -13
  355. udata/tests/features/territories/test_territories_api.py +71 -91
  356. udata/tests/forms/test_basic_fields.py +7 -7
  357. udata/tests/forms/test_current_user_field.py +39 -66
  358. udata/tests/forms/test_daterange_field.py +31 -39
  359. udata/tests/forms/test_dict_field.py +28 -26
  360. udata/tests/forms/test_extras_fields.py +102 -76
  361. udata/tests/forms/test_form_field.py +8 -8
  362. udata/tests/forms/test_image_field.py +33 -26
  363. udata/tests/forms/test_model_field.py +134 -123
  364. udata/tests/forms/test_model_list_field.py +7 -7
  365. udata/tests/forms/test_nested_model_list_field.py +117 -79
  366. udata/tests/forms/test_publish_as_field.py +36 -65
  367. udata/tests/forms/test_reference_field.py +34 -53
  368. udata/tests/forms/test_user_forms.py +23 -21
  369. udata/tests/forms/test_uuid_field.py +6 -10
  370. udata/tests/frontend/__init__.py +9 -6
  371. udata/tests/frontend/test_auth.py +7 -6
  372. udata/tests/frontend/test_csv.py +81 -96
  373. udata/tests/frontend/test_hooks.py +43 -43
  374. udata/tests/frontend/test_markdown.py +211 -191
  375. udata/tests/helpers.py +32 -37
  376. udata/tests/models.py +2 -2
  377. udata/tests/organization/test_csv_adapter.py +21 -16
  378. udata/tests/organization/test_notifications.py +11 -18
  379. udata/tests/organization/test_organization_model.py +13 -13
  380. udata/tests/organization/test_organization_rdf.py +29 -22
  381. udata/tests/organization/test_organization_tasks.py +16 -17
  382. udata/tests/plugin.py +76 -73
  383. udata/tests/reuse/test_reuse_model.py +21 -21
  384. udata/tests/reuse/test_reuse_task.py +11 -13
  385. udata/tests/search/__init__.py +11 -12
  386. udata/tests/search/test_adapter.py +60 -70
  387. udata/tests/search/test_query.py +16 -16
  388. udata/tests/search/test_results.py +10 -7
  389. udata/tests/site/test_site_api.py +11 -16
  390. udata/tests/site/test_site_metrics.py +20 -30
  391. udata/tests/site/test_site_model.py +4 -5
  392. udata/tests/site/test_site_rdf.py +94 -78
  393. udata/tests/test_activity.py +17 -17
  394. udata/tests/test_discussions.py +292 -299
  395. udata/tests/test_i18n.py +37 -40
  396. udata/tests/test_linkchecker.py +91 -85
  397. udata/tests/test_mail.py +13 -17
  398. udata/tests/test_migrations.py +219 -180
  399. udata/tests/test_model.py +164 -157
  400. udata/tests/test_notifications.py +17 -17
  401. udata/tests/test_owned.py +14 -14
  402. udata/tests/test_rdf.py +25 -23
  403. udata/tests/test_routing.py +89 -93
  404. udata/tests/test_storages.py +137 -128
  405. udata/tests/test_tags.py +44 -46
  406. udata/tests/test_topics.py +7 -7
  407. udata/tests/test_transfer.py +42 -49
  408. udata/tests/test_uris.py +160 -161
  409. udata/tests/test_utils.py +79 -71
  410. udata/tests/user/test_user_rdf.py +5 -9
  411. udata/tests/workers/test_jobs_commands.py +57 -58
  412. udata/tests/workers/test_tasks_routing.py +23 -29
  413. udata/tests/workers/test_workers_api.py +125 -131
  414. udata/tests/workers/test_workers_helpers.py +6 -6
  415. udata/tracking.py +4 -6
  416. udata/uris.py +45 -46
  417. udata/utils.py +68 -66
  418. udata/wsgi.py +1 -1
  419. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/METADATA +3 -2
  420. udata-9.1.2.dev30382.dist-info/RECORD +704 -0
  421. udata-9.1.2.dev30355.dist-info/RECORD +0 -704
  422. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/LICENSE +0 -0
  423. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/WHEEL +0 -0
  424. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/entry_points.txt +0 -0
  425. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30382.dist-info}/top_level.txt +0 -0
udata/core/topic/api.py CHANGED
@@ -1,134 +1,145 @@
1
- from udata.api import api, fields, API
1
+ from udata.api import API, api, fields
2
2
  from udata.core.dataset.api_fields import dataset_fields
3
3
  from udata.core.discussions.models import Discussion
4
4
  from udata.core.organization.api_fields import org_ref_fields
5
5
  from udata.core.reuse.api_fields import reuse_fields
6
6
  from udata.core.spatial.api_fields import spatial_coverage_fields
7
- from udata.core.topic.permissions import TopicEditPermission
8
7
  from udata.core.topic.parsers import TopicApiParser
8
+ from udata.core.topic.permissions import TopicEditPermission
9
9
  from udata.core.user.api_fields import user_ref_fields
10
10
 
11
- from .models import Topic
12
11
  from .forms import TopicForm
12
+ from .models import Topic
13
13
 
14
- DEFAULT_SORTING = '-created_at'
14
+ DEFAULT_SORTING = "-created_at"
15
15
 
16
- ns = api.namespace('topics', 'Topics related operations')
16
+ ns = api.namespace("topics", "Topics related operations")
17
17
 
18
- topic_fields = api.model('Topic', {
19
- 'id': fields.String(description='The topic identifier'),
20
- 'name': fields.String(description='The topic name', required=True),
21
- 'slug': fields.String(
22
- description='The topic permalink string', readonly=True),
23
- 'description': fields.Markdown(
24
- description='The topic description in Markdown', required=True),
25
- 'tags': fields.List(
26
- fields.String, description='Some keywords to help in search', required=True),
27
- 'datasets': fields.List(
28
- fields.Nested(dataset_fields),
29
- description='The topic datasets',
30
- attribute=lambda o: [d.fetch() for d in o.datasets],
31
- ),
32
- 'reuses': fields.List(
33
- fields.Nested(reuse_fields),
34
- description='The topic reuses',
35
- attribute=lambda o: [r.fetch() for r in o.reuses],
36
- ),
37
- 'featured': fields.Boolean(description='Is the topic featured'),
38
- 'private': fields.Boolean(description='Is the topic private'),
39
- 'created_at': fields.ISODateTime(
40
- description='The topic creation date', readonly=True),
41
- 'spatial': fields.Nested(
42
- spatial_coverage_fields, allow_null=True,
43
- description='The spatial coverage'),
44
- 'last_modified': fields.ISODateTime(
45
- description='The topic last modification date', readonly=True),
46
- 'organization': fields.Nested(
47
- org_ref_fields, allow_null=True,
48
- description='The publishing organization', readonly=True),
49
- 'owner': fields.Nested(
50
- user_ref_fields, description='The owner user', readonly=True,
51
- allow_null=True),
52
- 'uri': fields.UrlFor(
53
- 'api.topic', lambda o: {'topic': o},
54
- description='The topic API URI', readonly=True),
55
- 'page': fields.UrlFor(
56
- 'topics.display', lambda o: {'topic': o},
57
- description='The topic page URL', readonly=True, fallback_endpoint='api.topic'),
58
- 'extras': fields.Raw(description='Extras attributes as key-value pairs'),
59
- }, mask='*,datasets{id,title,uri,page},reuses{id,title,image,image_thumbnail,uri,page}')
18
+ topic_fields = api.model(
19
+ "Topic",
20
+ {
21
+ "id": fields.String(description="The topic identifier"),
22
+ "name": fields.String(description="The topic name", required=True),
23
+ "slug": fields.String(description="The topic permalink string", readonly=True),
24
+ "description": fields.Markdown(
25
+ description="The topic description in Markdown", required=True
26
+ ),
27
+ "tags": fields.List(
28
+ fields.String, description="Some keywords to help in search", required=True
29
+ ),
30
+ "datasets": fields.List(
31
+ fields.Nested(dataset_fields),
32
+ description="The topic datasets",
33
+ attribute=lambda o: [d.fetch() for d in o.datasets],
34
+ ),
35
+ "reuses": fields.List(
36
+ fields.Nested(reuse_fields),
37
+ description="The topic reuses",
38
+ attribute=lambda o: [r.fetch() for r in o.reuses],
39
+ ),
40
+ "featured": fields.Boolean(description="Is the topic featured"),
41
+ "private": fields.Boolean(description="Is the topic private"),
42
+ "created_at": fields.ISODateTime(description="The topic creation date", readonly=True),
43
+ "spatial": fields.Nested(
44
+ spatial_coverage_fields, allow_null=True, description="The spatial coverage"
45
+ ),
46
+ "last_modified": fields.ISODateTime(
47
+ description="The topic last modification date", readonly=True
48
+ ),
49
+ "organization": fields.Nested(
50
+ org_ref_fields,
51
+ allow_null=True,
52
+ description="The publishing organization",
53
+ readonly=True,
54
+ ),
55
+ "owner": fields.Nested(
56
+ user_ref_fields, description="The owner user", readonly=True, allow_null=True
57
+ ),
58
+ "uri": fields.UrlFor(
59
+ "api.topic", lambda o: {"topic": o}, description="The topic API URI", readonly=True
60
+ ),
61
+ "page": fields.UrlFor(
62
+ "topics.display",
63
+ lambda o: {"topic": o},
64
+ description="The topic page URL",
65
+ readonly=True,
66
+ fallback_endpoint="api.topic",
67
+ ),
68
+ "extras": fields.Raw(description="Extras attributes as key-value pairs"),
69
+ },
70
+ mask="*,datasets{id,title,uri,page},reuses{id,title,image,image_thumbnail,uri,page}",
71
+ )
60
72
 
61
- topic_page_fields = api.model('TopicPage', fields.pager(topic_fields))
73
+ topic_page_fields = api.model("TopicPage", fields.pager(topic_fields))
62
74
 
63
75
  topic_parser = TopicApiParser()
64
76
 
65
77
 
66
- @ns.route('/', endpoint='topics')
78
+ @ns.route("/", endpoint="topics")
67
79
  class TopicsAPI(API):
68
80
  """
69
81
  Warning: querying a list with a topic containing a lot of related objects (datasets, reuses)
70
82
  will fail/take a lot of time because every object is dereferenced. Use api v2 if you can.
71
83
  """
72
84
 
73
- @api.doc('list_topics')
85
+ @api.doc("list_topics")
74
86
  @api.expect(topic_parser.parser)
75
87
  @api.marshal_with(topic_page_fields)
76
88
  def get(self):
77
- '''List all topics'''
89
+ """List all topics"""
78
90
  args = topic_parser.parse()
79
91
  topics = Topic.objects()
80
92
  topics = topic_parser.parse_filters(topics, args)
81
- sort = args['sort'] or ('$text_score' if args['q'] else None) or DEFAULT_SORTING
82
- return (topics.order_by(sort)
83
- .paginate(args['page'], args['page_size']))
93
+ sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
94
+ return topics.order_by(sort).paginate(args["page"], args["page_size"])
84
95
 
85
- @api.doc('create_topic')
96
+ @api.doc("create_topic")
86
97
  @api.expect(topic_fields)
87
98
  @api.marshal_with(topic_fields)
88
- @api.response(400, 'Validation error')
99
+ @api.response(400, "Validation error")
89
100
  def post(self):
90
- '''Create a topic'''
101
+ """Create a topic"""
91
102
  form = api.validate(TopicForm)
92
103
  return form.save(), 201
93
104
 
94
105
 
95
- @ns.route('/<topic:topic>/', endpoint='topic')
96
- @api.param('topic', 'The topic ID or slug')
97
- @api.response(404, 'Object not found')
106
+ @ns.route("/<topic:topic>/", endpoint="topic")
107
+ @api.param("topic", "The topic ID or slug")
108
+ @api.response(404, "Object not found")
98
109
  class TopicAPI(API):
99
110
  """
100
111
  Warning: querying a topic containing a lot of related objects (datasets, reuses)
101
112
  will fail/take a lot of time because every object is dereferenced. Use api v2 if you can.
102
113
  """
103
114
 
104
- @api.doc('get_topic')
115
+ @api.doc("get_topic")
105
116
  @api.marshal_with(topic_fields)
106
117
  def get(self, topic):
107
- '''Get a given topic'''
118
+ """Get a given topic"""
108
119
  return topic
109
120
 
110
121
  @api.secure
111
- @api.doc('update_topic')
122
+ @api.doc("update_topic")
112
123
  @api.expect(topic_fields)
113
124
  @api.marshal_with(topic_fields)
114
- @api.response(400, 'Validation error')
115
- @api.response(403, 'Forbidden')
125
+ @api.response(400, "Validation error")
126
+ @api.response(403, "Forbidden")
116
127
  def put(self, topic):
117
- '''Update a given topic'''
128
+ """Update a given topic"""
118
129
  if not TopicEditPermission(topic).can():
119
- api.abort(403, 'Forbidden')
130
+ api.abort(403, "Forbidden")
120
131
  form = api.validate(TopicForm, topic)
121
132
  return form.save()
122
133
 
123
134
  @api.secure
124
- @api.doc('delete_topic')
125
- @api.response(204, 'Object deleted')
126
- @api.response(403, 'Forbidden')
135
+ @api.doc("delete_topic")
136
+ @api.response(204, "Object deleted")
137
+ @api.response(403, "Forbidden")
127
138
  def delete(self, topic):
128
- '''Delete a given topic'''
139
+ """Delete a given topic"""
129
140
  if not TopicEditPermission(topic).can():
130
- api.abort(403, 'Forbidden')
141
+ api.abort(403, "Forbidden")
131
142
  # Remove discussions linked to the topic
132
143
  Discussion.objects(subject=topic).delete()
133
144
  topic.delete()
134
- return '', 204
145
+ return "", 204
udata/core/topic/apiv2.py CHANGED
@@ -1,11 +1,10 @@
1
1
  import logging
2
2
 
3
3
  import mongoengine
4
-
5
4
  from bson import ObjectId
6
- from flask import url_for, request
5
+ from flask import request, url_for
7
6
 
8
- from udata.api import apiv2, API, fields
7
+ from udata.api import API, apiv2, fields
9
8
  from udata.core.dataset.api import DatasetApiParser
10
9
  from udata.core.dataset.apiv2 import dataset_page_fields
11
10
  from udata.core.dataset.models import Dataset
@@ -19,142 +18,169 @@ from udata.core.topic.parsers import TopicApiParser
19
18
  from udata.core.topic.permissions import TopicEditPermission
20
19
  from udata.core.user.api_fields import user_ref_fields
21
20
 
22
- DEFAULT_SORTING = '-created_at'
21
+ DEFAULT_SORTING = "-created_at"
23
22
  DEFAULT_PAGE_SIZE = 20
24
23
 
25
24
  log = logging.getLogger(__name__)
26
25
 
27
- ns = apiv2.namespace('topics', 'Topics related operations')
26
+ ns = apiv2.namespace("topics", "Topics related operations")
28
27
 
29
28
  topic_parser = TopicApiParser()
30
29
  generic_parser = apiv2.page_parser()
31
30
  dataset_parser = DatasetApiParser()
32
31
  reuse_parser = ReuseApiParser()
33
32
 
34
- common_doc = {
35
- 'params': {'topic': 'The topic ID'}
36
- }
37
-
38
-
39
- topic_fields = apiv2.model('Topic', {
40
- 'id': fields.String(description='The topic identifier'),
41
- 'name': fields.String(description='The topic name', required=True),
42
- 'slug': fields.String(
43
- description='The topic permalink string', readonly=True),
44
- 'description': fields.Markdown(
45
- description='The topic description in Markdown', required=True),
46
- 'tags': fields.List(
47
- fields.String, description='Some keywords to help in search', required=True),
48
-
49
- 'datasets': fields.Raw(attribute=lambda o: {
50
- 'rel': 'subsection',
51
- 'href': url_for('apiv2.topic_datasets', topic=o.id, page=1,
52
- page_size=DEFAULT_PAGE_SIZE, _external=True),
53
- 'type': 'GET',
54
- 'total': len(o.datasets)
55
- }, description='Link to the topic datasets'),
56
- 'reuses': fields.Raw(attribute=lambda o: {
57
- 'rel': 'subsection',
58
- 'href': url_for('apiv2.topic_reuses', topic=o.id, page=1,
59
- page_size=DEFAULT_PAGE_SIZE, _external=True),
60
- 'type': 'GET',
61
- 'total': len(o.reuses)
62
- }, description='Link to the topic reuses'),
63
- 'featured': fields.Boolean(description='Is the topic featured'),
64
- 'private': fields.Boolean(description='Is the topic private'),
65
- 'created_at': fields.ISODateTime(
66
- description='The topic creation date', readonly=True),
67
- 'spatial': fields.Nested(
68
- spatial_coverage_fields, allow_null=True,
69
- description='The spatial coverage'),
70
- 'last_modified': fields.ISODateTime(
71
- description='The topic last modification date', readonly=True),
72
- 'organization': fields.Nested(
73
- org_ref_fields, allow_null=True,
74
- description='The publishing organization', readonly=True),
75
- 'owner': fields.Nested(
76
- user_ref_fields, description='The owner user', readonly=True,
77
- allow_null=True),
78
- 'uri': fields.UrlFor(
79
- 'api.topic', lambda o: {'topic': o},
80
- description='The topic API URI', readonly=True),
81
- 'page': fields.UrlFor(
82
- 'topics.display', lambda o: {'topic': o},
83
- description='The topic page URL', readonly=True, fallback_endpoint='api.topic'),
84
- 'extras': fields.Raw(description='Extras attributes as key-value pairs'),
85
- })
86
-
87
- topic_page_fields = apiv2.model('TopicPage', fields.pager(topic_fields))
88
-
89
-
90
- @ns.route('/', endpoint='topics_list')
33
+ common_doc = {"params": {"topic": "The topic ID"}}
34
+
35
+
36
+ topic_fields = apiv2.model(
37
+ "Topic",
38
+ {
39
+ "id": fields.String(description="The topic identifier"),
40
+ "name": fields.String(description="The topic name", required=True),
41
+ "slug": fields.String(description="The topic permalink string", readonly=True),
42
+ "description": fields.Markdown(
43
+ description="The topic description in Markdown", required=True
44
+ ),
45
+ "tags": fields.List(
46
+ fields.String, description="Some keywords to help in search", required=True
47
+ ),
48
+ "datasets": fields.Raw(
49
+ attribute=lambda o: {
50
+ "rel": "subsection",
51
+ "href": url_for(
52
+ "apiv2.topic_datasets",
53
+ topic=o.id,
54
+ page=1,
55
+ page_size=DEFAULT_PAGE_SIZE,
56
+ _external=True,
57
+ ),
58
+ "type": "GET",
59
+ "total": len(o.datasets),
60
+ },
61
+ description="Link to the topic datasets",
62
+ ),
63
+ "reuses": fields.Raw(
64
+ attribute=lambda o: {
65
+ "rel": "subsection",
66
+ "href": url_for(
67
+ "apiv2.topic_reuses",
68
+ topic=o.id,
69
+ page=1,
70
+ page_size=DEFAULT_PAGE_SIZE,
71
+ _external=True,
72
+ ),
73
+ "type": "GET",
74
+ "total": len(o.reuses),
75
+ },
76
+ description="Link to the topic reuses",
77
+ ),
78
+ "featured": fields.Boolean(description="Is the topic featured"),
79
+ "private": fields.Boolean(description="Is the topic private"),
80
+ "created_at": fields.ISODateTime(description="The topic creation date", readonly=True),
81
+ "spatial": fields.Nested(
82
+ spatial_coverage_fields, allow_null=True, description="The spatial coverage"
83
+ ),
84
+ "last_modified": fields.ISODateTime(
85
+ description="The topic last modification date", readonly=True
86
+ ),
87
+ "organization": fields.Nested(
88
+ org_ref_fields,
89
+ allow_null=True,
90
+ description="The publishing organization",
91
+ readonly=True,
92
+ ),
93
+ "owner": fields.Nested(
94
+ user_ref_fields, description="The owner user", readonly=True, allow_null=True
95
+ ),
96
+ "uri": fields.UrlFor(
97
+ "api.topic", lambda o: {"topic": o}, description="The topic API URI", readonly=True
98
+ ),
99
+ "page": fields.UrlFor(
100
+ "topics.display",
101
+ lambda o: {"topic": o},
102
+ description="The topic page URL",
103
+ readonly=True,
104
+ fallback_endpoint="api.topic",
105
+ ),
106
+ "extras": fields.Raw(description="Extras attributes as key-value pairs"),
107
+ },
108
+ )
109
+
110
+ topic_page_fields = apiv2.model("TopicPage", fields.pager(topic_fields))
111
+
112
+
113
+ @ns.route("/", endpoint="topics_list")
91
114
  class TopicsAPI(API):
92
115
  @apiv2.expect(topic_parser.parser)
93
116
  @apiv2.marshal_with(topic_page_fields)
94
117
  def get(self):
95
- '''List all topics'''
118
+ """List all topics"""
96
119
  args = topic_parser.parse()
97
120
  topics = Topic.objects()
98
121
  topics = topic_parser.parse_filters(topics, args)
99
- sort = args['sort'] or ('$text_score' if args['q'] else None) or DEFAULT_SORTING
100
- return (topics.order_by(sort)
101
- .paginate(args['page'], args['page_size']))
122
+ sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
123
+ return topics.order_by(sort).paginate(args["page"], args["page_size"])
102
124
 
103
125
 
104
- @ns.route('/<topic:topic>/', endpoint='topic', doc=common_doc)
105
- @apiv2.response(404, 'Topic not found')
126
+ @ns.route("/<topic:topic>/", endpoint="topic", doc=common_doc)
127
+ @apiv2.response(404, "Topic not found")
106
128
  class TopicAPI(API):
107
- @apiv2.doc('get_topic')
129
+ @apiv2.doc("get_topic")
108
130
  @apiv2.marshal_with(topic_fields)
109
131
  def get(self, topic):
110
- '''Get a given topic'''
132
+ """Get a given topic"""
111
133
  return topic
112
134
 
113
135
 
114
- topic_add_items_fields = apiv2.model('TopicItemsAdd', {
115
- 'id': fields.String(description='Id of the item to add', required=True),
116
- }, location='json')
136
+ topic_add_items_fields = apiv2.model(
137
+ "TopicItemsAdd",
138
+ {
139
+ "id": fields.String(description="Id of the item to add", required=True),
140
+ },
141
+ location="json",
142
+ )
117
143
 
118
144
 
119
- @ns.route('/<topic:topic>/datasets/', endpoint='topic_datasets', doc=common_doc)
145
+ @ns.route("/<topic:topic>/datasets/", endpoint="topic_datasets", doc=common_doc)
120
146
  class TopicDatasetsAPI(API):
121
- @apiv2.doc('topic_datasets')
147
+ @apiv2.doc("topic_datasets")
122
148
  @apiv2.expect(dataset_parser.parser)
123
149
  @apiv2.marshal_with(dataset_page_fields)
124
150
  def get(self, topic):
125
- '''Get a given topic datasets, with filters'''
151
+ """Get a given topic datasets, with filters"""
126
152
  args = dataset_parser.parse()
127
- args['topic'] = topic.id
153
+ args["topic"] = topic.id
128
154
  datasets = Dataset.objects(archived=None, deleted=None, private=False)
129
155
  datasets = dataset_parser.parse_filters(datasets, args)
130
- sort = args['sort'] or ('$text_score' if args['q'] else None) or '-created_at_internal'
131
- return datasets.order_by(sort).paginate(args['page'], args['page_size'])
156
+ sort = args["sort"] or ("$text_score" if args["q"] else None) or "-created_at_internal"
157
+ return datasets.order_by(sort).paginate(args["page"], args["page_size"])
132
158
 
133
159
  @apiv2.secure
134
- @apiv2.doc('topic_datasets_create')
160
+ @apiv2.doc("topic_datasets_create")
135
161
  @apiv2.expect([topic_add_items_fields])
136
162
  @apiv2.marshal_with(topic_fields)
137
- @apiv2.response(400, 'Malformed object id(s) in request')
138
- @apiv2.response(400, 'Expecting a list')
139
- @apiv2.response(400, 'Expecting a list of dicts with id attribute')
140
- @apiv2.response(404, 'Topic not found')
141
- @apiv2.response(403, 'Forbidden')
163
+ @apiv2.response(400, "Malformed object id(s) in request")
164
+ @apiv2.response(400, "Expecting a list")
165
+ @apiv2.response(400, "Expecting a list of dicts with id attribute")
166
+ @apiv2.response(404, "Topic not found")
167
+ @apiv2.response(403, "Forbidden")
142
168
  def post(self, topic):
143
169
  if not TopicEditPermission(topic).can():
144
- apiv2.abort(403, 'Forbidden')
170
+ apiv2.abort(403, "Forbidden")
145
171
 
146
172
  data = request.json
147
173
 
148
174
  if not isinstance(data, list):
149
- apiv2.abort(400, 'Expecting a list')
150
- if not all(isinstance(d, dict) and d.get('id') for d in data):
151
- apiv2.abort(400, 'Expecting a list of dicts with id attribute')
175
+ apiv2.abort(400, "Expecting a list")
176
+ if not all(isinstance(d, dict) and d.get("id") for d in data):
177
+ apiv2.abort(400, "Expecting a list of dicts with id attribute")
152
178
 
153
179
  try:
154
- datasets = Dataset.objects.filter(id__in=[d['id'] for d in data]).only('id')
180
+ datasets = Dataset.objects.filter(id__in=[d["id"] for d in data]).only("id")
155
181
  diff = set(d.id for d in datasets) - set(d.id for d in topic.datasets)
156
182
  except mongoengine.errors.ValidationError:
157
- apiv2.abort(400, 'Malformed object id(s) in request')
183
+ apiv2.abort(400, "Malformed object id(s) in request")
158
184
 
159
185
  if diff:
160
186
  topic.datasets += [ObjectId(did) for did in diff]
@@ -163,69 +189,71 @@ class TopicDatasetsAPI(API):
163
189
  return topic, 201
164
190
 
165
191
 
166
- @ns.route('/<topic:topic>/datasets/<dataset:dataset>/', endpoint='topic_dataset', doc={
167
- 'params': {'topic': 'The topic ID', 'dataset': 'The dataset ID'}
168
- })
192
+ @ns.route(
193
+ "/<topic:topic>/datasets/<dataset:dataset>/",
194
+ endpoint="topic_dataset",
195
+ doc={"params": {"topic": "The topic ID", "dataset": "The dataset ID"}},
196
+ )
169
197
  class TopicDatasetAPI(API):
170
198
  @apiv2.secure
171
- @apiv2.response(404, 'Topic not found')
172
- @apiv2.response(404, 'Dataset not found in topic')
173
- @apiv2.response(204, 'Success')
199
+ @apiv2.response(404, "Topic not found")
200
+ @apiv2.response(404, "Dataset not found in topic")
201
+ @apiv2.response(204, "Success")
174
202
  def delete(self, topic, dataset):
175
- '''Delete a given dataset from the given topic'''
203
+ """Delete a given dataset from the given topic"""
176
204
  if not TopicEditPermission(topic).can():
177
- apiv2.abort(403, 'Forbidden')
205
+ apiv2.abort(403, "Forbidden")
178
206
 
179
207
  if dataset.id not in (d.id for d in topic.datasets):
180
- apiv2.abort(404, 'Dataset not found in topic')
208
+ apiv2.abort(404, "Dataset not found in topic")
181
209
  topic.datasets = [d for d in topic.datasets if d.id != dataset.id]
182
210
  topic.save()
183
211
 
184
212
  return None, 204
185
213
 
186
214
 
187
- @ns.route('/<topic:topic>/reuses/', endpoint='topic_reuses', doc=common_doc)
215
+ @ns.route("/<topic:topic>/reuses/", endpoint="topic_reuses", doc=common_doc)
188
216
  class TopicReusesAPI(API):
189
- @apiv2.doc('topic_reuses')
217
+ @apiv2.doc("topic_reuses")
190
218
  @apiv2.expect(reuse_parser.parser)
191
219
  @apiv2.marshal_with(reuse_page_fields)
192
220
  def get(self, topic):
193
- '''Get a given topic reuses, with filters'''
221
+ """Get a given topic reuses, with filters"""
194
222
  args = reuse_parser.parse()
195
223
  reuses = Reuse.objects(deleted=None, private__ne=True).filter(
196
224
  id__in=[d.id for d in topic.reuses]
197
225
  )
198
226
  # warning: topic in reuse_parser is different from Topic
199
227
  reuses = reuse_parser.parse_filters(reuses, args)
200
- sort = args['sort'] or ('$text_score' if args['q'] else None) or DEFAULT_SORTING
201
- return reuses.order_by(sort).paginate(args['page'], args['page_size'])
228
+ sort = args["sort"] or ("$text_score" if args["q"] else None) or DEFAULT_SORTING
229
+ return reuses.order_by(sort).paginate(args["page"], args["page_size"])
202
230
 
203
231
  @apiv2.secure
204
- @apiv2.doc('topic_reuses_create')
232
+ @apiv2.doc("topic_reuses_create")
205
233
  @apiv2.expect([topic_add_items_fields])
206
234
  @apiv2.marshal_with(topic_fields)
207
- @apiv2.response(400, 'Malformed object id(s) in request')
208
- @apiv2.response(400, 'Expecting a list')
209
- @apiv2.response(400, 'Expecting a list of dicts with id attribute')
210
- @apiv2.response(404, 'Topic not found')
211
- @apiv2.response(403, 'Forbidden')
235
+ @apiv2.response(400, "Malformed object id(s) in request")
236
+ @apiv2.response(400, "Expecting a list")
237
+ @apiv2.response(400, "Expecting a list of dicts with id attribute")
238
+ @apiv2.response(404, "Topic not found")
239
+ @apiv2.response(403, "Forbidden")
212
240
  def post(self, topic):
213
- '''Add reuses to a given topic from a list of reuses ids'''
241
+ """Add reuses to a given topic from a list of reuses ids"""
214
242
  if not TopicEditPermission(topic).can():
215
- apiv2.abort(403, 'Forbidden')
243
+ apiv2.abort(403, "Forbidden")
216
244
 
217
245
  data = request.json
218
246
 
219
247
  if not isinstance(data, list):
220
- apiv2.abort(400, 'Expecting a list')
221
- if not all(isinstance(d, dict) and d.get('id') for d in data):
222
- apiv2.abort(400, 'Expecting a list of dicts with id attribute')
248
+ apiv2.abort(400, "Expecting a list")
249
+ if not all(isinstance(d, dict) and d.get("id") for d in data):
250
+ apiv2.abort(400, "Expecting a list of dicts with id attribute")
223
251
 
224
252
  try:
225
- reuses = Reuse.objects.filter(id__in=[r['id'] for r in data]).only('id')
253
+ reuses = Reuse.objects.filter(id__in=[r["id"] for r in data]).only("id")
226
254
  diff = set(d.id for d in reuses) - set(d.id for d in topic.reuses)
227
255
  except mongoengine.errors.ValidationError:
228
- apiv2.abort(400, 'Malformed object id(s) in request')
256
+ apiv2.abort(400, "Malformed object id(s) in request")
229
257
 
230
258
  if diff:
231
259
  topic.reuses += [ObjectId(rid) for rid in diff]
@@ -234,21 +262,23 @@ class TopicReusesAPI(API):
234
262
  return topic, 201
235
263
 
236
264
 
237
- @ns.route('/<topic:topic>/reuses/<reuse:reuse>/', endpoint='topic_reuse', doc={
238
- 'params': {'topic': 'The topic ID', 'reuse': 'The reuse ID'}
239
- })
265
+ @ns.route(
266
+ "/<topic:topic>/reuses/<reuse:reuse>/",
267
+ endpoint="topic_reuse",
268
+ doc={"params": {"topic": "The topic ID", "reuse": "The reuse ID"}},
269
+ )
240
270
  class TopicReuseAPI(API):
241
271
  @apiv2.secure
242
- @apiv2.response(404, 'Topic not found')
243
- @apiv2.response(404, 'Reuse not found in topic')
244
- @apiv2.response(204, 'Success')
272
+ @apiv2.response(404, "Topic not found")
273
+ @apiv2.response(404, "Reuse not found in topic")
274
+ @apiv2.response(204, "Success")
245
275
  def delete(self, topic, reuse):
246
- '''Delete a given reuse from the given topic'''
276
+ """Delete a given reuse from the given topic"""
247
277
  if not TopicEditPermission(topic).can():
248
- apiv2.abort(403, 'Forbidden')
278
+ apiv2.abort(403, "Forbidden")
249
279
 
250
280
  if reuse.id not in (d.id for d in topic.reuses):
251
- apiv2.abort(404, 'Reuse not found in topic')
281
+ apiv2.abort(404, "Reuse not found in topic")
252
282
  topic.reuses = [d for d in topic.reuses if d.id != reuse.id]
253
283
  topic.save()
254
284
 
@@ -12,10 +12,9 @@ class TopicFactory(ModelFactory):
12
12
  class Meta:
13
13
  model = Topic
14
14
 
15
- name = factory.Faker('sentence')
16
- description = factory.Faker('text')
17
- tags = factory.LazyAttribute(lambda o: [utils.unique_string(16)
18
- for _ in range(3)])
15
+ name = factory.Faker("sentence")
16
+ description = factory.Faker("text")
17
+ tags = factory.LazyAttribute(lambda o: [utils.unique_string(16) for _ in range(3)])
19
18
  private = False
20
19
 
21
20
  @factory.lazy_attribute