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/commands/static.py CHANGED
@@ -1,13 +1,11 @@
1
1
  import logging
2
2
  import os
3
3
  import shutil
4
-
5
- import click
6
-
7
4
  from glob import iglob
8
5
  from os import makedirs
9
- from os.path import exists, join, isdir
6
+ from os.path import exists, isdir, join
10
7
 
8
+ import click
11
9
  from flask import current_app
12
10
 
13
11
  from udata.commands import cli
@@ -16,21 +14,21 @@ log = logging.getLogger(__name__)
16
14
 
17
15
 
18
16
  @cli.command()
19
- @click.argument('path', default='public')
20
- @click.option('-ni', '--no-input', is_flag=True, help="Disable input prompts")
17
+ @click.argument("path", default="public")
18
+ @click.option("-ni", "--no-input", is_flag=True, help="Disable input prompts")
21
19
  def collect(path, no_input):
22
- '''Collect static files'''
20
+ """Collect static files"""
23
21
  if exists(path):
24
22
  msg = '"%s" directory already exists and will be erased'
25
23
  log.warning(msg, path)
26
24
  if not no_input:
27
- click.confirm('Are you sure?', abort=True)
25
+ click.confirm("Are you sure?", abort=True)
28
26
 
29
27
  log.info('Deleting static directory "%s"', path)
30
28
  shutil.rmtree(path)
31
29
 
32
30
  prefix = current_app.static_url_path or current_app.static_folder
33
- if prefix.startswith('/'):
31
+ if prefix.startswith("/"):
34
32
  prefix = prefix[1:]
35
33
  destination = join(path, prefix)
36
34
  log.info('Copying application assets into "%s"', destination)
@@ -39,29 +37,29 @@ def collect(path, no_input):
39
37
  for blueprint in current_app.blueprints.values():
40
38
  if blueprint.has_static_folder:
41
39
  prefix = current_app.static_prefixes.get(blueprint.name)
42
- prefix = prefix or blueprint.url_prefix or ''
43
- prefix += blueprint.static_url_path or ''
44
- if prefix.startswith('/'):
40
+ prefix = prefix or blueprint.url_prefix or ""
41
+ prefix += blueprint.static_url_path or ""
42
+ if prefix.startswith("/"):
45
43
  prefix = prefix[1:]
46
44
 
47
- log.info('Copying %s assets to %s', blueprint.name, prefix)
45
+ log.info("Copying %s assets to %s", blueprint.name, prefix)
48
46
  destination = join(path, prefix)
49
47
  copy_recursive(blueprint.static_folder, destination)
50
48
 
51
- for prefix, source in current_app.config['STATIC_DIRS']:
52
- log.info('Copying %s to %s', source, prefix)
49
+ for prefix, source in current_app.config["STATIC_DIRS"]:
50
+ log.info("Copying %s to %s", source, prefix)
53
51
  destination = join(path, prefix)
54
52
  copy_recursive(source, destination)
55
53
 
56
- log.info('Done')
54
+ log.info("Done")
57
55
 
58
56
 
59
57
  def copy_recursive(source, destination):
60
58
  if not exists(destination):
61
59
  makedirs(destination)
62
- for filename in iglob(join(source, '*')):
60
+ for filename in iglob(join(source, "*")):
63
61
  if isdir(filename):
64
- suffix = filename.replace(source, '')
62
+ suffix = filename.replace(source, "")
65
63
  if suffix.startswith(os.sep):
66
64
  suffix = suffix[1:]
67
65
  copy_recursive(filename, join(destination, suffix))
udata/commands/test.py CHANGED
@@ -1,33 +1,33 @@
1
1
  import logging
2
2
 
3
- from . import cli, success, error, header
3
+ from . import cli, error, header, success
4
4
 
5
5
  logger = logging.getLogger(__name__)
6
6
 
7
7
 
8
- @cli.group('test')
8
+ @cli.group("test")
9
9
  def test():
10
- '''Some commands for testing purpose'''
10
+ """Some commands for testing purpose"""
11
11
 
12
12
 
13
13
  @test.command()
14
14
  def log():
15
- '''Test logging'''
16
- header('header é')
17
- success('success é')
18
- error('error é')
19
- error('error with string details é', 'ééé')
20
- error('error with object details é', Exception('ééé'))
21
- logger.debug('debug é')
22
- logger.info('info é')
23
- logger.info('info é with unicode interpolation %s', 'ééé')
24
- logger.info('info é with interpolations %s %d %f', 'ééé', 10, .1)
25
- logger.info('success é')
26
- logger.info('info\nmulti\nlines é')
27
- logger.warning('warning é')
28
- logger.error('error é')
29
- logger.critical('critical é')
15
+ """Test logging"""
16
+ header("header é")
17
+ success("success é")
18
+ error("error é")
19
+ error("error with string details é", "ééé")
20
+ error("error with object details é", Exception("ééé"))
21
+ logger.debug("debug é")
22
+ logger.info("info é")
23
+ logger.info("info é with unicode interpolation %s", "ééé")
24
+ logger.info("info é with interpolations %s %d %f", "ééé", 10, 0.1)
25
+ logger.info("success é")
26
+ logger.info("info\nmulti\nlines é")
27
+ logger.warning("warning é")
28
+ logger.error("error é")
29
+ logger.critical("critical é")
30
30
  try:
31
- raise Exception('An exception é')
31
+ raise Exception("An exception é")
32
32
  except Exception:
33
- logger.exception('exception é')
33
+ logger.exception("exception é")
@@ -3,37 +3,39 @@ from tempfile import NamedTemporaryFile
3
3
 
4
4
  from udata import models
5
5
  from udata.commands.fixtures import generate_fixtures
6
- from udata.tests import TestCase, DBTestMixin
6
+ from udata.tests import DBTestMixin, TestCase
7
7
 
8
8
 
9
9
  class FixturesTest(DBTestMixin, TestCase):
10
-
11
10
  def test_generate_fixtures(self):
12
11
  with NamedTemporaryFile(delete=True) as fixtures_fd:
13
- json_fixtures = [{
14
- "resources": [{
15
- "description": "test description",
16
- "filetype": "remote",
12
+ json_fixtures = [
13
+ {
14
+ "resources": [
15
+ {
16
+ "description": "test description",
17
+ "filetype": "remote",
18
+ "title": "test",
19
+ "url": "https://dev.local",
20
+ }
21
+ ],
22
+ "dataset": {
23
+ "description": "### Le Test",
24
+ "frequency": "punctual",
25
+ "tags": ["action-publique"],
17
26
  "title": "test",
18
- "url": "https://dev.local"
19
- }],
20
- "dataset": {
21
- "description": "### Le Test",
22
- "frequency": "punctual",
23
- "tags": ["action-publique"],
24
- "title": "test"
25
27
  },
26
- "organization": {
27
- "description": "test description",
28
- "name": "Test"
29
- },
30
- "reuses": [{
31
- "description": "test description",
32
- "title": "test",
33
- "url": "https://dev.local"
34
- }]
35
- }]
36
- with open(fixtures_fd, 'w') as f:
28
+ "organization": {"description": "test description", "name": "Test"},
29
+ "reuses": [
30
+ {
31
+ "description": "test description",
32
+ "title": "test",
33
+ "url": "https://dev.local",
34
+ }
35
+ ],
36
+ }
37
+ ]
38
+ with open(fixtures_fd, "w") as f:
37
39
  json.dump(json_fixtures, f)
38
40
  generate_fixtures(fixtures_fd)
39
41
  self.assertEqual(models.Organization.objects.count(), 1)
udata/commands/worker.py CHANGED
@@ -5,7 +5,6 @@ from urllib.parse import urlparse
5
5
 
6
6
  import click
7
7
  import redis
8
-
9
8
  from flask import current_app
10
9
 
11
10
  from udata.commands import cli, exit_with_error
@@ -14,15 +13,15 @@ from udata.tasks import celery, router
14
13
  log = logging.getLogger(__name__)
15
14
 
16
15
 
17
- @cli.group('worker')
16
+ @cli.group("worker")
18
17
  def grp():
19
- '''Worker related operations'''
18
+ """Worker related operations"""
20
19
  pass
21
20
 
22
21
 
23
22
  @grp.command()
24
23
  def start():
25
- '''Start a worker'''
24
+ """Start a worker"""
26
25
  worker = celery.Worker()
27
26
  worker.start()
28
27
  return worker.exitcode
@@ -31,27 +30,27 @@ def start():
31
30
  def status_print_task(count, biggest_task_name, munin=False):
32
31
  if munin:
33
32
  # Munin expect all values, including zeros
34
- print('%s.value %s' % (format_field_for_munin(count[0]), count[1]))
33
+ print("%s.value %s" % (format_field_for_munin(count[0]), count[1]))
35
34
  elif count[1] > 0:
36
35
  # We only display tasks with items in queue for readability
37
- print('* %s : %s' % (count[0].ljust(biggest_task_name), count[1]))
36
+ print("* %s : %s" % (count[0].ljust(biggest_task_name), count[1]))
38
37
 
39
38
 
40
39
  def status_print_config(queue):
41
40
  if not queue:
42
- exit_with_error('--munin-config called without a --queue parameter')
41
+ exit_with_error("--munin-config called without a --queue parameter")
43
42
  tasks = [n for n, q in get_tasks().items() if q == queue]
44
- print('graph_title Waiting tasks for queue %s' % queue)
45
- print('graph_vlabel Nb of tasks')
46
- print('graph_category celery')
43
+ print("graph_title Waiting tasks for queue %s" % queue)
44
+ print("graph_vlabel Nb of tasks")
45
+ print("graph_category celery")
47
46
  for task in tasks:
48
- print('%s.label %s' % (format_field_for_munin(task), short_name(task)))
47
+ print("%s.label %s" % (format_field_for_munin(task), short_name(task)))
49
48
 
50
49
 
51
50
  def status_print_queue(queue, munin=False):
52
51
  r = get_redis_connection()
53
52
  if not munin:
54
- print('-' * 40)
53
+ print("-" * 40)
55
54
  queue_length = r.llen(queue)
56
55
  if not munin:
57
56
  print('Queue "%s": %s task(s)' % (queue, queue_length))
@@ -59,7 +58,7 @@ def status_print_queue(queue, munin=False):
59
58
  biggest_task_name = 0
60
59
  for task in r.lrange(queue, 0, -1):
61
60
  task = json.loads(task)
62
- task_name = task['headers']['task']
61
+ task_name = task["headers"]["task"]
63
62
  if len(task_name) > biggest_task_name:
64
63
  biggest_task_name = len(task_name)
65
64
  counter[task_name] += 1
@@ -68,63 +67,62 @@ def status_print_queue(queue, munin=False):
68
67
 
69
68
 
70
69
  def format_field_for_munin(field):
71
- return field.replace('.', '__').replace('-', '_')
70
+ return field.replace(".", "__").replace("-", "_")
72
71
 
73
72
 
74
73
  def get_queues(queue):
75
- queues = [q.name for q in current_app.config['CELERY_TASK_QUEUES']]
74
+ queues = [q.name for q in current_app.config["CELERY_TASK_QUEUES"]]
76
75
  if queue:
77
76
  queues = [q for q in queues if q == queue]
78
77
  if not len(queues):
79
- exit_with_error('Error: no queue found')
78
+ exit_with_error("Error: no queue found")
80
79
  return queues
81
80
 
82
81
 
83
82
  def get_redis_connection():
84
- parsed_url = urlparse(current_app.config['CELERY_BROKER_URL'])
83
+ parsed_url = urlparse(current_app.config["CELERY_BROKER_URL"])
85
84
  db = parsed_url.path[1:] if parsed_url.path else 0
86
- return redis.StrictRedis(host=parsed_url.hostname, port=parsed_url.port,
87
- db=db)
85
+ return redis.StrictRedis(host=parsed_url.hostname, port=parsed_url.port, db=db)
88
86
 
89
87
 
90
88
  def get_task_queue(name, cls):
91
- return (router(name, [], {}, None, task=cls) or {}).get('queue', 'default')
89
+ return (router(name, [], {}, None, task=cls) or {}).get("queue", "default")
92
90
 
93
91
 
94
92
  def short_name(name):
95
- if '.' not in name:
93
+ if "." not in name:
96
94
  return name
97
- return name.rsplit('.', 1)[1]
95
+ return name.rsplit(".", 1)[1]
98
96
 
99
97
 
100
98
  def get_tasks():
101
- '''Get a list of known tasks with their routing queue'''
99
+ """Get a list of known tasks with their routing queue"""
102
100
  return {
103
101
  name: get_task_queue(name, cls)
104
102
  for name, cls in celery.tasks.items()
105
103
  # Exclude celery internal tasks
106
- if not name.startswith('celery.')
104
+ if not name.startswith("celery.")
107
105
  # Exclude udata test tasks
108
- and not name.startswith('test-')
106
+ and not name.startswith("test-")
109
107
  }
110
108
 
111
109
 
112
110
  @grp.command()
113
111
  def tasks():
114
- '''Display registered tasks with their queue'''
112
+ """Display registered tasks with their queue"""
115
113
  tasks = get_tasks()
116
114
  longest = max(tasks.keys(), key=len)
117
115
  size = len(longest)
118
116
  for name, queue in sorted(tasks.items()):
119
- print('* {0}: {1}'.format(name.ljust(size), queue))
117
+ print("* {0}: {1}".format(name.ljust(size), queue))
120
118
 
121
119
 
122
120
  @grp.command()
123
- @click.option('-q', '--queue', help='Queue to be analyzed', default=None)
124
- @click.option('-m', '--munin', is_flag=True,
125
- help='Output in a munin plugin compatible format')
126
- @click.option('-c', '--munin-config', is_flag=True,
127
- help='Output in a munin plugin config compatible format')
121
+ @click.option("-q", "--queue", help="Queue to be analyzed", default=None)
122
+ @click.option("-m", "--munin", is_flag=True, help="Output in a munin plugin compatible format")
123
+ @click.option(
124
+ "-c", "--munin-config", is_flag=True, help="Output in a munin plugin config compatible format"
125
+ )
128
126
  def status(queue, munin, munin_config):
129
127
  """List queued tasks aggregated by name"""
130
128
  if munin_config:
@@ -133,4 +131,4 @@ def status(queue, munin, munin_config):
133
131
  for queue in queues:
134
132
  status_print_queue(queue, munin=munin)
135
133
  if not munin:
136
- print('-' * 40)
134
+ print("-" * 40)
udata/core/__init__.py CHANGED
@@ -1,21 +1,21 @@
1
1
  from importlib import import_module
2
2
 
3
3
  MODULES = (
4
- 'metrics',
5
- 'storages',
6
- 'user',
7
- 'dataset',
8
- 'reuse',
9
- 'organization',
10
- 'activity',
11
- 'followers',
12
- 'topic',
13
- 'post',
4
+ "metrics",
5
+ "storages",
6
+ "user",
7
+ "dataset",
8
+ "reuse",
9
+ "organization",
10
+ "activity",
11
+ "followers",
12
+ "topic",
13
+ "post",
14
14
  )
15
15
 
16
16
 
17
17
  def init_app(app):
18
18
  for module in MODULES:
19
- module = import_module('udata.core.{0}'.format(module))
20
- if hasattr(module, 'init_app'):
19
+ module = import_module("udata.core.{0}".format(module))
20
+ if hasattr(module, "init_app"):
21
21
  module.init_app(app)
@@ -1,6 +1,5 @@
1
1
  import logging
2
2
 
3
-
4
3
  log = logging.getLogger(__name__)
5
4
 
6
5
 
@@ -2,75 +2,85 @@ import logging
2
2
 
3
3
  from mongoengine.errors import DoesNotExist
4
4
 
5
- from udata.api import api, API, fields
6
- from udata.models import db, Activity
7
-
8
- from udata.core.user.api_fields import user_ref_fields
5
+ from udata.api import API, api, fields
9
6
  from udata.core.organization.api_fields import org_ref_fields
7
+ from udata.core.user.api_fields import user_ref_fields
8
+ from udata.models import Activity, db
10
9
 
11
10
  log = logging.getLogger(__name__)
12
11
 
13
- activity_fields = api.model('Activity', {
14
- 'actor': fields.Nested(
15
- user_ref_fields,
16
- description='The user who performed the action', readonly=True),
17
- 'organization': fields.Nested(
18
- org_ref_fields, allow_null=True, readonly=True,
19
- description='The organization who performed the action'),
20
- 'related_to': fields.String(
21
- attribute='related_to',
22
- description='The activity target name', required=True),
23
- 'related_to_id': fields.String(
24
- attribute='related_to.id',
25
- description='The activity target object identifier', required=True),
26
- 'related_to_kind': fields.String(
27
- attribute='related_to.__class__.__name__',
28
- description='The activity target object class name', required=True),
29
- 'related_to_url': fields.String(
30
- attribute='related_to.display_url',
31
- description='The activity target model', required=True),
32
- 'created_at': fields.ISODateTime(
33
- description='When the action has been performed', readonly=True),
34
- 'label': fields.String(
35
- description='The label of the activity', required=True),
36
- 'key': fields.String(
37
- description='The key of the activity', required=True),
38
- 'icon': fields.String(
39
- description='The icon of the activity', required=True),
40
- 'extras': fields.Raw(description='Extras attributes as key-value pairs'),
41
- })
12
+ activity_fields = api.model(
13
+ "Activity",
14
+ {
15
+ "actor": fields.Nested(
16
+ user_ref_fields, description="The user who performed the action", readonly=True
17
+ ),
18
+ "organization": fields.Nested(
19
+ org_ref_fields,
20
+ allow_null=True,
21
+ readonly=True,
22
+ description="The organization who performed the action",
23
+ ),
24
+ "related_to": fields.String(
25
+ attribute="related_to", description="The activity target name", required=True
26
+ ),
27
+ "related_to_id": fields.String(
28
+ attribute="related_to.id",
29
+ description="The activity target object identifier",
30
+ required=True,
31
+ ),
32
+ "related_to_kind": fields.String(
33
+ attribute="related_to.__class__.__name__",
34
+ description="The activity target object class name",
35
+ required=True,
36
+ ),
37
+ "related_to_url": fields.String(
38
+ attribute="related_to.display_url",
39
+ description="The activity target model",
40
+ required=True,
41
+ ),
42
+ "created_at": fields.ISODateTime(
43
+ description="When the action has been performed", readonly=True
44
+ ),
45
+ "label": fields.String(description="The label of the activity", required=True),
46
+ "key": fields.String(description="The key of the activity", required=True),
47
+ "icon": fields.String(description="The icon of the activity", required=True),
48
+ "extras": fields.Raw(description="Extras attributes as key-value pairs"),
49
+ },
50
+ )
42
51
 
43
- activity_page_fields = api.model('ActivityPage', fields.pager(activity_fields))
52
+ activity_page_fields = api.model("ActivityPage", fields.pager(activity_fields))
44
53
 
45
54
  activity_parser = api.page_parser()
46
55
  activity_parser.add_argument(
47
- 'user', type=str, help='Filter activities for that particular user',
48
- location='args')
56
+ "user", type=str, help="Filter activities for that particular user", location="args"
57
+ )
49
58
  activity_parser.add_argument(
50
- 'organization', type=str,
51
- help='Filter activities for that particular organization',
52
- location='args')
59
+ "organization",
60
+ type=str,
61
+ help="Filter activities for that particular organization",
62
+ location="args",
63
+ )
53
64
 
54
65
 
55
- @api.route('/activity', endpoint='activity')
66
+ @api.route("/activity", endpoint="activity")
56
67
  class SiteActivityAPI(API):
57
- @api.doc('activity')
68
+ @api.doc("activity")
58
69
  @api.expect(activity_parser)
59
70
  @api.marshal_list_with(activity_page_fields)
60
71
  def get(self):
61
- '''Fetch site activity, optionally filtered by user of org.'''
72
+ """Fetch site activity, optionally filtered by user of org."""
62
73
  args = activity_parser.parse_args()
63
74
  qs = Activity.objects
64
75
 
65
- if args['organization']:
66
- qs = qs(db.Q(organization=args['organization']) |
67
- db.Q(related_to=args['organization']))
76
+ if args["organization"]:
77
+ qs = qs(db.Q(organization=args["organization"]) | db.Q(related_to=args["organization"]))
68
78
 
69
- if args['user']:
70
- qs = qs(actor=args['user'])
79
+ if args["user"]:
80
+ qs = qs(actor=args["user"])
71
81
 
72
- qs = qs.order_by('-created_at')
73
- qs = qs.paginate(args['page'], args['page_size'])
82
+ qs = qs.order_by("-created_at")
83
+ qs = qs.paginate(args["page"], args["page_size"])
74
84
 
75
85
  # Filter out DBRefs
76
86
  # Always return a result even not complete
@@ -3,23 +3,22 @@ from datetime import datetime
3
3
  from blinker import Signal
4
4
  from mongoengine.signals import post_save
5
5
 
6
- from udata.mongo import db
7
6
  from udata.auth import current_user
7
+ from udata.mongo import db
8
8
 
9
9
  from .signals import new_activity
10
10
 
11
-
12
- __all__ = ('Activity', )
11
+ __all__ = ("Activity",)
13
12
 
14
13
 
15
14
  _registered_activities = {}
16
15
 
17
16
 
18
17
  class EmitNewActivityMetaClass(db.BaseDocumentMetaclass):
19
- '''Ensure any child class dispatches the on_new signal'''
18
+ """Ensure any child class dispatches the on_new signal"""
19
+
20
20
  def __new__(cls, name, bases, attrs):
21
- new_class = super(EmitNewActivityMetaClass, cls).__new__(
22
- cls, name, bases, attrs)
21
+ new_class = super(EmitNewActivityMetaClass, cls).__new__(cls, name, bases, attrs)
23
22
  if new_class.key:
24
23
  post_save.connect(cls.post_save, sender=new_class)
25
24
  _registered_activities[new_class.key] = new_class
@@ -31,9 +30,10 @@ class EmitNewActivityMetaClass(db.BaseDocumentMetaclass):
31
30
 
32
31
 
33
32
  class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
34
- '''Store the activity entries for a single related object'''
35
- actor = db.ReferenceField('User', required=True)
36
- organization = db.ReferenceField('Organization')
33
+ """Store the activity entries for a single related object"""
34
+
35
+ actor = db.ReferenceField("User", required=True)
36
+ organization = db.ReferenceField("Organization")
37
37
  related_to = db.ReferenceField(db.DomainModel, required=True)
38
38
  created_at = db.DateTimeField(default=datetime.utcnow, required=True)
39
39
 
@@ -42,23 +42,23 @@ class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
42
42
  on_new = Signal()
43
43
 
44
44
  meta = {
45
- 'indexes': [
46
- 'actor',
47
- 'organization',
48
- 'related_to',
49
- '-created_at',
50
- ('actor', '-created_at'),
51
- ('organization', '-created_at'),
52
- ('related_to', '-created_at'),
45
+ "indexes": [
46
+ "actor",
47
+ "organization",
48
+ "related_to",
49
+ "-created_at",
50
+ ("actor", "-created_at"),
51
+ ("organization", "-created_at"),
52
+ ("related_to", "-created_at"),
53
53
  ],
54
- 'allow_inheritance': True,
54
+ "allow_inheritance": True,
55
55
  }
56
56
 
57
57
  key = None
58
58
  label = None
59
- badge_type = 'primary'
60
- icon = 'fa fa-info-circle'
61
- template = 'activity/base.html'
59
+ badge_type = "primary"
60
+ icon = "fa fa-info-circle"
61
+ template = "activity/base.html"
62
62
 
63
63
  @classmethod
64
64
  def connect(cls, func):
@@ -66,8 +66,10 @@ class Activity(db.Document, metaclass=EmitNewActivityMetaClass):
66
66
 
67
67
  @classmethod
68
68
  def emit(cls, related_to, organization=None, extras=None):
69
- new_activity.send(cls,
70
- related_to=related_to,
71
- actor=current_user._get_current_object(),
72
- organization=organization,
73
- extras=extras)
69
+ new_activity.send(
70
+ cls,
71
+ related_to=related_to,
72
+ actor=current_user._get_current_object(),
73
+ organization=organization,
74
+ extras=extras,
75
+ )
@@ -6,4 +6,4 @@ from blinker import Namespace
6
6
  ns = Namespace()
7
7
 
8
8
  #: Trigerred on new activity/action
9
- new_activity = ns.signal('new-activity')
9
+ new_activity = ns.signal("new-activity")