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
@@ -2,16 +2,14 @@ import json
2
2
  import logging
3
3
  import signal
4
4
  import sys
5
-
6
5
  from collections import Counter
7
6
  from contextlib import contextmanager
8
7
  from datetime import datetime
9
8
  from textwrap import dedent
10
- import requests
11
9
 
12
10
  import click
11
+ import requests
13
12
  import slugify
14
-
15
13
  from mongoengine import errors
16
14
  from mongoengine.context_managers import switch_collection
17
15
 
@@ -23,22 +21,22 @@ from udata.core.spatial.models import GeoLevel, GeoZone, SpatialCoverage
23
21
  log = logging.getLogger(__name__)
24
22
 
25
23
 
26
- DEFAULT_GEOZONES_FILE = 'https://www.data.gouv.fr/fr/datasets/r/a1bb263a-6cc7-4871-ab4f-2470235a67bf'
27
- DEFAULT_LEVELS_FILE = 'https://www.data.gouv.fr/fr/datasets/r/e0206442-78b3-4a00-b71c-c065d20561c8'
24
+ DEFAULT_GEOZONES_FILE = (
25
+ "https://www.data.gouv.fr/fr/datasets/r/a1bb263a-6cc7-4871-ab4f-2470235a67bf"
26
+ )
27
+ DEFAULT_LEVELS_FILE = "https://www.data.gouv.fr/fr/datasets/r/e0206442-78b3-4a00-b71c-c065d20561c8"
28
28
 
29
29
 
30
- @cli.group('spatial')
30
+ @cli.group("spatial")
31
31
  def grp():
32
- '''Geospatial related operations'''
32
+ """Geospatial related operations"""
33
33
  pass
34
34
 
35
35
 
36
36
  def load_levels(col, json_levels):
37
37
  for i, level in enumerate(json_levels):
38
- col.objects(id=level['id']).modify(
39
- upsert=True,
40
- set__name=level['label'],
41
- set__admin_level=level.get('admin_level')
38
+ col.objects(id=level["id"]).modify(
39
+ upsert=True, set__name=level["label"], set__admin_level=level.get("admin_level")
42
40
  )
43
41
  return i
44
42
 
@@ -46,72 +44,71 @@ def load_levels(col, json_levels):
46
44
  def load_zones(col, json_geozones):
47
45
  loaded_geozones = 0
48
46
  for _, geozone in enumerate(json_geozones):
49
- if geozone.get('is_deleted', False):
47
+ if geozone.get("is_deleted", False):
50
48
  continue
51
49
  params = {
52
- 'slug': slugify.slugify(geozone['nom'], separator='-'),
53
- 'level': str(geozone['level']),
54
- 'code': geozone['codeINSEE'],
55
- 'name': geozone['nom'],
56
- 'uri': geozone['uri']
50
+ "slug": slugify.slugify(geozone["nom"], separator="-"),
51
+ "level": str(geozone["level"]),
52
+ "code": geozone["codeINSEE"],
53
+ "name": geozone["nom"],
54
+ "uri": geozone["uri"],
57
55
  }
58
56
  try:
59
- col.objects(id=geozone['_id']).modify(upsert=True, **{
60
- 'set__{0}'.format(k): v for k, v in params.items()
61
- })
57
+ col.objects(id=geozone["_id"]).modify(
58
+ upsert=True, **{"set__{0}".format(k): v for k, v in params.items()}
59
+ )
62
60
  loaded_geozones += 1
63
61
  except errors.ValidationError as e:
64
- log.warning('Validation error (%s) for %s with %s',
65
- e, geozone['nom'], params)
62
+ log.warning("Validation error (%s) for %s with %s", e, geozone["nom"], params)
66
63
  continue
67
64
  return loaded_geozones
68
65
 
69
66
 
70
67
  @contextmanager
71
68
  def handle_error(to_delete=None):
72
- '''
69
+ """
73
70
  Handle errors while loading.
74
71
  In case of error, properly log it, remove the temporary files and collections and exit.
75
72
  If `to_delete` is given a collection, it will be deleted deleted.
76
- '''
73
+ """
77
74
  # Handle keyboard interrupt
78
75
  signal.signal(signal.SIGINT, signal.default_int_handler)
79
76
  signal.signal(signal.SIGTERM, signal.default_int_handler)
80
77
  try:
81
78
  yield
82
79
  except KeyboardInterrupt:
83
- print('') # Proper warning message under the "^C" display
84
- log.warning('Interrupted by signal')
80
+ print("") # Proper warning message under the "^C" display
81
+ log.warning("Interrupted by signal")
85
82
  except Exception as e:
86
83
  log.error(e)
87
84
  else:
88
85
  return # Nothing to do in case of success
89
86
  if to_delete:
90
- log.info('Removing temporary collection %s', to_delete._get_collection_name())
87
+ log.info("Removing temporary collection %s", to_delete._get_collection_name())
91
88
  to_delete.drop_collection()
92
89
  sys.exit(-1)
93
90
 
94
91
 
95
92
  @grp.command()
96
- @click.argument('geozones-file', default=DEFAULT_GEOZONES_FILE)
97
- @click.argument('levels-file', default=DEFAULT_LEVELS_FILE)
98
- @click.option('-d', '--drop', is_flag=True, help='Drop existing data')
93
+ @click.argument("geozones-file", default=DEFAULT_GEOZONES_FILE)
94
+ @click.argument("levels-file", default=DEFAULT_LEVELS_FILE)
95
+ @click.option("-d", "--drop", is_flag=True, help="Drop existing data")
99
96
  def load(geozones_file, levels_file, drop=False):
100
- '''
97
+ """
101
98
  Load a geozones archive from <filename>
102
99
 
103
100
  <filename> can be either a local path or a remote URL.
104
- '''
105
- log.info('Loading GeoZones levels')
106
- if levels_file.startswith('http'):
101
+ """
102
+ log.info("Loading GeoZones levels")
103
+ if levels_file.startswith("http"):
107
104
  json_levels = requests.get(levels_file).json()
108
105
  else:
109
106
  with open(levels_file) as f:
110
107
  json_levels = json.load(f)
111
108
 
112
- ts = datetime.utcnow().isoformat().replace('-', '').replace(':', '').split('.')[0]
109
+ ts = datetime.utcnow().isoformat().replace("-", "").replace(":", "").split(".")[0]
113
110
  if drop and GeoLevel.objects.count():
114
- name = '_'.join((GeoLevel._get_collection_name(), ts))
111
+ name = "_".join((GeoLevel._get_collection_name(), ts))
115
112
  target = GeoLevel._get_collection_name()
116
113
  with switch_collection(GeoLevel, name):
117
114
  with handle_error(GeoLevel):
@@ -120,17 +117,17 @@ def load(geozones_file, levels_file, drop=False):
120
117
  else:
121
118
  with handle_error():
122
119
  total = load_levels(GeoLevel, json_levels)
123
- log.info('Loaded {total} levels'.format(total=total))
120
+ log.info("Loaded {total} levels".format(total=total))
124
121
 
125
- log.info('Loading Zones')
126
- if geozones_file.startswith('http'):
122
+ log.info("Loading Zones")
123
+ if geozones_file.startswith("http"):
127
124
  json_geozones = requests.get(geozones_file).json()
128
125
  else:
129
126
  with open(geozones_file) as f:
130
127
  json_geozones = json.load(f)
131
128
 
132
129
  if drop and GeoZone.objects.count():
133
- name = '_'.join((GeoZone._get_collection_name(), ts))
130
+ name = "_".join((GeoZone._get_collection_name(), ts))
134
131
  target = GeoZone._get_collection_name()
135
132
  with switch_collection(GeoZone, name):
136
133
  with handle_error(GeoZone):
@@ -139,35 +136,35 @@ def load(geozones_file, levels_file, drop=False):
139
136
  else:
140
137
  with handle_error():
141
138
  total = load_zones(GeoZone, json_geozones)
142
- log.info('Loaded {total} zones'.format(total=total))
139
+ log.info("Loaded {total} zones".format(total=total))
143
140
 
144
- log.info('Clean removed geozones in datasets')
141
+ log.info("Clean removed geozones in datasets")
145
142
  count = fixup_removed_geozone()
146
- log.info(f'{count} geozones removed from datasets')
143
+ log.info(f"{count} geozones removed from datasets")
147
144
 
148
145
 
149
146
  @grp.command()
150
147
  def migrate():
151
- '''
148
+ """
152
149
  Migrate zones from old to new ids in datasets.
153
150
 
154
151
  Should only be run once with the new version of geozones w/ geohisto.
155
- '''
156
- counter = Counter(['zones', 'datasets'])
157
- qs = GeoZone.objects.only('id', 'level')
152
+ """
153
+ counter = Counter(["zones", "datasets"])
154
+ qs = GeoZone.objects.only("id", "level")
158
155
  # Fetch datasets with non-empty spatial zones
159
156
  for dataset in Dataset.objects(spatial__zones__gt=[]):
160
- counter['datasets'] += 1
157
+ counter["datasets"] += 1
161
158
  new_zones = []
162
159
  for current_zone in dataset.spatial.zones:
163
- counter['zones'] += 1
160
+ counter["zones"] += 1
164
161
 
165
162
  level, code = geoids.parse(current_zone.id)
166
163
  zone = qs(level=level, code=code).first() or qs(code=code).first()
167
164
 
168
165
  if not zone:
169
- log.warning('No match for %s: skipped', current_zone.id)
170
- counter['skipped'] += 1
166
+ log.warning("No match for %s: skipped", current_zone.id)
167
+ counter["skipped"] += 1
171
168
  continue
172
169
 
173
170
  new_zones.append(zone.id)
@@ -175,30 +172,37 @@ def migrate():
175
172
 
176
173
  # Update dataset with new spatial zones
177
174
  dataset.update(
178
- spatial=SpatialCoverage(
179
- granularity=dataset.spatial.granularity,
180
- zones=list(new_zones)
181
- )
175
+ spatial=SpatialCoverage(granularity=dataset.spatial.granularity, zones=list(new_zones))
182
176
  )
183
177
 
184
- level_summary = '\n'.join([
185
- ' - {0}: {1}'.format(l.id, counter[l.id])
186
- for l in GeoLevel.objects.order_by('admin_level')
187
- ])
188
- summary = '\n'.join([dedent('''\
178
+ level_summary = "\n".join(
179
+ [
180
+ " - {0}: {1}".format(l.id, counter[l.id])
181
+ for l in GeoLevel.objects.order_by("admin_level")
182
+ ]
183
+ )
184
+ summary = "\n".join(
185
+ [
186
+ dedent(
187
+ """\
189
188
  Summary
190
189
  =======
191
190
  Processed {zones} zones in {datasets} datasets:\
192
- '''.format(level_summary, **counter)), level_summary])
191
+ """.format(level_summary, **counter)
192
+ ),
193
+ level_summary,
194
+ ]
195
+ )
193
196
  log.info(summary)
194
- log.info('Done')
197
+ log.info("Done")
198
+
195
199
 
196
200
  def fixup_removed_geozone():
197
201
  count = 0
198
202
  all_datasets = Dataset.objects(spatial__zones__0__exists=True).timeout(False)
199
203
  for dataset in all_datasets:
200
204
  zones = dataset.spatial.zones
201
- new_zones = [z for z in zones if getattr(z, 'name', None) is not None]
205
+ new_zones = [z for z in zones if getattr(z, "name", None) is not None]
202
206
 
203
207
  if len(new_zones) < len(zones):
204
208
  log.debug(f"Removing deleted zones from dataset '{dataset.title}'")
@@ -207,4 +211,3 @@ def fixup_removed_geozone():
207
211
  dataset.save()
208
212
 
209
213
  return count
210
-
@@ -1,9 +1,9 @@
1
1
  from udata.i18n import L_
2
2
 
3
3
  BASE_GRANULARITIES = [
4
- ('poi', L_('POI')),
5
- ('other', L_('Other')),
4
+ ("poi", L_("POI")),
5
+ ("other", L_("Other")),
6
6
  ]
7
7
 
8
8
  ADMIN_LEVEL_MIN = 1
9
- ADMIN_LEVEL_MAX = 110
9
+ ADMIN_LEVEL_MAX = 110
@@ -1,7 +1,5 @@
1
1
  import factory
2
-
3
2
  from faker.providers import BaseProvider
4
-
5
3
  from geojson.utils import generate_random
6
4
 
7
5
  from udata.factories import DateRangeFactory, ModelFactory
@@ -13,90 +11,75 @@ from .models import GeoLevel, GeoZone, SpatialCoverage, spatial_granularities
13
11
 
14
12
  @faker_provider
15
13
  class GeoJsonProvider(BaseProvider):
16
- '''A Fake GeoJSON provider'''
14
+ """A Fake GeoJSON provider"""
17
15
 
18
16
  def random_range(self, min=2, max=5):
19
17
  return range(self.random_int(min, max))
20
18
 
21
19
  def point(self):
22
- return generate_random('Point')
20
+ return generate_random("Point")
23
21
 
24
22
  def linestring(self):
25
- return generate_random('LineString')
23
+ return generate_random("LineString")
26
24
 
27
25
  def polygon(self):
28
- return generate_random('Polygon')
26
+ return generate_random("Polygon")
29
27
 
30
28
  def multipoint(self):
31
- coordinates = [
32
- generate_random('Point')['coordinates']
33
- for _ in self.random_range()
34
- ]
29
+ coordinates = [generate_random("Point")["coordinates"] for _ in self.random_range()]
35
30
 
36
- return {
37
- 'type': 'MultiPoint',
38
- 'coordinates': coordinates
39
- }
31
+ return {"type": "MultiPoint", "coordinates": coordinates}
40
32
 
41
33
  def multilinestring(self):
42
- coordinates = [
43
- generate_random('LineString')['coordinates']
44
- for _ in self.random_range()
45
- ]
34
+ coordinates = [generate_random("LineString")["coordinates"] for _ in self.random_range()]
46
35
 
47
- return {
48
- 'type': 'MultiLineString',
49
- 'coordinates': coordinates
50
- }
36
+ return {"type": "MultiLineString", "coordinates": coordinates}
51
37
 
52
38
  def multipolygon(self):
53
- coordinates = [
54
- generate_random('Polygon')['coordinates']
55
- for _ in self.random_range()
56
- ]
39
+ coordinates = [generate_random("Polygon")["coordinates"] for _ in self.random_range()]
57
40
 
58
- return {
59
- 'type': 'MultiPolygon',
60
- 'coordinates': coordinates
61
- }
41
+ return {"type": "MultiPolygon", "coordinates": coordinates}
62
42
 
63
43
  def geometry_collection(self):
64
44
  element_factories = [
65
- self.point, self.linestring, self.polygon,
66
- self.multipoint, self.multilinestring, self.multipolygon
45
+ self.point,
46
+ self.linestring,
47
+ self.polygon,
48
+ self.multipoint,
49
+ self.multilinestring,
50
+ self.multipolygon,
67
51
  ]
68
52
  return {
69
- 'type': 'GeometryCollection',
70
- 'geometries': [
71
- self.random_element(element_factories)()
72
- for _ in self.random_range()
73
- ]
53
+ "type": "GeometryCollection",
54
+ "geometries": [self.random_element(element_factories)() for _ in self.random_range()],
74
55
  }
75
56
 
76
57
  def feature(self):
77
58
  element_factories = [
78
- self.point, self.linestring, self.polygon,
79
- self.multipoint, self.multilinestring, self.multipolygon
59
+ self.point,
60
+ self.linestring,
61
+ self.polygon,
62
+ self.multipoint,
63
+ self.multilinestring,
64
+ self.multipolygon,
80
65
  ]
81
66
  return {
82
- 'type': 'Feature',
83
- 'geometry': self.random_element(element_factories)(),
84
- 'properties': {},
67
+ "type": "Feature",
68
+ "geometry": self.random_element(element_factories)(),
69
+ "properties": {},
85
70
  }
86
71
 
87
72
  def feature_collection(self):
88
73
  return {
89
- 'type': 'FeatureCollection',
90
- 'features': [self.feature() for _ in self.random_range()]
74
+ "type": "FeatureCollection",
75
+ "features": [self.feature() for _ in self.random_range()],
91
76
  }
92
77
 
93
78
 
94
79
  @faker_provider
95
80
  class SpatialProvider(BaseProvider):
96
81
  def spatial_granularity(self):
97
- return self.generator.random_element([
98
- row[0] for row in spatial_granularities
99
- ])
82
+ return self.generator.random_element([row[0] for row in spatial_granularities])
100
83
 
101
84
 
102
85
  class GeoZoneFactory(ModelFactory):
@@ -104,10 +87,10 @@ class GeoZoneFactory(ModelFactory):
104
87
  model = GeoZone
105
88
 
106
89
  id = factory.LazyAttribute(geoids.from_zone)
107
- name = factory.Faker('city')
108
- slug = factory.Faker('slug')
109
- code = factory.Faker('zipcode')
110
- uri = factory.Faker('url')
90
+ name = factory.Faker("city")
91
+ slug = factory.Faker("slug")
92
+ code = factory.Faker("zipcode")
93
+ uri = factory.Faker("url")
111
94
  level = factory.LazyAttribute(lambda o: GeoLevelFactory().id)
112
95
 
113
96
 
@@ -116,12 +99,12 @@ class SpatialCoverageFactory(ModelFactory):
116
99
  model = SpatialCoverage
117
100
 
118
101
  zones = factory.LazyAttribute(lambda o: [GeoZoneFactory()])
119
- granularity = factory.Faker('spatial_granularity')
102
+ granularity = factory.Faker("spatial_granularity")
120
103
 
121
104
 
122
105
  class GeoLevelFactory(ModelFactory):
123
106
  class Meta:
124
107
  model = GeoLevel
125
108
 
126
- id = factory.Faker('unique_string')
127
- name = factory.Faker('name')
109
+ id = factory.Faker("unique_string")
110
+ name = factory.Faker("name")
@@ -1,9 +1,10 @@
1
- import geojson
2
1
  import json
3
2
  import logging
4
3
 
5
- from udata.forms import widgets, ModelForm, validators
6
- from udata.forms.fields import ModelList, Field, SelectField, FormField
4
+ import geojson
5
+
6
+ from udata.forms import ModelForm, validators, widgets
7
+ from udata.forms.fields import Field, FormField, ModelList, SelectField
7
8
  from udata.i18n import lazy_gettext as _
8
9
 
9
10
  from .models import GeoZone, SpatialCoverage, spatial_granularities
@@ -12,15 +13,14 @@ log = logging.getLogger(__name__)
12
13
 
13
14
 
14
15
  class ZonesAutocompleter(widgets.TextInput):
15
- classes = 'zone-completer'
16
+ classes = "zone-completer"
16
17
 
17
18
  def __call__(self, field, **kwargs):
18
- '''Store the values as JSON to prefeed selectize'''
19
+ """Store the values as JSON to prefeed selectize"""
19
20
  if field.data:
20
- kwargs['data-values'] = json.dumps([{
21
- 'id': zone.id,
22
- 'name': zone.name
23
- } for zone in field.data])
21
+ kwargs["data-values"] = json.dumps(
22
+ [{"id": zone.id, "name": zone.name} for zone in field.data]
23
+ )
24
24
  return super(ZonesAutocompleter, self).__call__(field, **kwargs)
25
25
 
26
26
 
@@ -29,12 +29,12 @@ class ZonesField(ModelList, Field):
29
29
  widget = ZonesAutocompleter()
30
30
 
31
31
  def fetch_objects(self, geoids):
32
- '''
32
+ """
33
33
  Custom object retrieval.
34
34
 
35
35
  Zones are resolved from their identifier
36
36
  instead of the default bulk fetch by ID.
37
- '''
37
+ """
38
38
  zones = []
39
39
  no_match = []
40
40
  for geoid in geoids:
@@ -45,8 +45,9 @@ class ZonesField(ModelList, Field):
45
45
  no_match.append(geoid)
46
46
 
47
47
  if no_match:
48
- msg = _('Unknown geoid(s): {identifiers}').format(
49
- identifiers=', '.join(str(id) for id in no_match))
48
+ msg = _("Unknown geoid(s): {identifiers}").format(
49
+ identifiers=", ".join(str(id) for id in no_match)
50
+ )
50
51
  raise validators.ValidationError(msg)
51
52
 
52
53
  return zones
@@ -63,15 +64,15 @@ class GeomField(Field):
63
64
  self.data = geojson.GeoJSON.to_instance(value)
64
65
  except:
65
66
  self.data = None
66
- log.exception('Unable to parse GeoJSON')
67
- raise ValueError(self.gettext('Not a valid GeoJSON'))
67
+ log.exception("Unable to parse GeoJSON")
68
+ raise ValueError(self.gettext("Not a valid GeoJSON"))
68
69
 
69
70
  def pre_validate(self, form):
70
71
  if self.data:
71
72
  if not isinstance(self.data, geojson.GeoJSON):
72
73
  self.data = geojson.GeoJSON.to_instance(self.data)
73
74
  if not isinstance(self.data, geojson.GeoJSON):
74
- raise validators.ValidationError('Not a valid GeoJSON')
75
+ raise validators.ValidationError("Not a valid GeoJSON")
75
76
  if not self.data.is_valid:
76
77
  raise validators.ValidationError(self.data.errors())
77
78
  return True
@@ -80,18 +81,18 @@ class GeomField(Field):
80
81
  class SpatialCoverageForm(ModelForm):
81
82
  model_class = SpatialCoverage
82
83
 
83
- zones = ZonesField(_('Spatial coverage'),
84
- description=_('A list of covered territories'),
85
- default=[])
86
- granularity = SelectField(_('Spatial granularity'),
87
- description=_('The size of the data increment'),
88
- choices=lambda: spatial_granularities,
89
- default='other')
84
+ zones = ZonesField(
85
+ _("Spatial coverage"), description=_("A list of covered territories"), default=[]
86
+ )
87
+ granularity = SelectField(
88
+ _("Spatial granularity"),
89
+ description=_("The size of the data increment"),
90
+ choices=lambda: spatial_granularities,
91
+ default="other",
92
+ )
90
93
  geom = GeomField()
91
94
 
92
95
 
93
96
  class SpatialCoverageField(FormField):
94
97
  def __init__(self, *args, **kwargs):
95
- super(SpatialCoverageField, self).__init__(SpatialCoverageForm,
96
- *args,
97
- **kwargs)
98
+ super(SpatialCoverageField, self).__init__(SpatialCoverageForm, *args, **kwargs)
@@ -1,42 +1,42 @@
1
- '''
1
+ """
2
2
  This module centralize GeoID resources and helpers.
3
3
 
4
4
  See https://github.com/etalab/geoids for more details
5
- '''
5
+ """
6
6
 
7
-
8
- __all__ = ('GeoIDError', 'parse', 'build', 'from_zone')
7
+ __all__ = ("GeoIDError", "parse", "build", "from_zone")
9
8
 
10
9
 
11
10
  class GeoIDError(ValueError):
12
- '''Raised when an error occur while parsing or building a GeoID'''
11
+ """Raised when an error occur while parsing or building a GeoID"""
12
+
13
13
  pass
14
14
 
15
15
 
16
16
  def parse(text):
17
- '''Parse a geoid from text and return a tuple (level, code, validity)'''
17
+ """Parse a geoid from text and return a tuple (level, code, validity)"""
18
18
  # Kept validity parsing for legacy parsing and migration.
19
19
  # Validity is parsed but ignored.
20
- if '@' in text:
21
- spatial, validity = text.split('@')
20
+ if "@" in text:
21
+ spatial, validity = text.split("@")
22
22
  else:
23
23
  spatial = text
24
- spatial = spatial.lower().replace('/', ':') # Backward compatibility
25
- if ':' not in spatial:
26
- raise GeoIDError('Bad GeoID format: {0}'.format(text))
24
+ spatial = spatial.lower().replace("/", ":") # Backward compatibility
25
+ if ":" not in spatial:
26
+ raise GeoIDError("Bad GeoID format: {0}".format(text))
27
27
  # country-subset is a special case:
28
- if spatial.startswith('country-subset:'):
29
- level, code = spatial.split(':', 1)
28
+ if spatial.startswith("country-subset:"):
29
+ level, code = spatial.split(":", 1)
30
30
  else:
31
- level, code = spatial.rsplit(':', 1)
31
+ level, code = spatial.rsplit(":", 1)
32
32
  return level, code
33
33
 
34
34
 
35
35
  def build(level, code):
36
- '''Serialize a GeoID from its parts'''
37
- return ':'.join((level, code))
36
+ """Serialize a GeoID from its parts"""
37
+ return ":".join((level, code))
38
38
 
39
39
 
40
40
  def from_zone(zone):
41
- '''Build a GeoID from a given zone'''
41
+ """Build a GeoID from a given zone"""
42
42
  return build(zone.level, zone.code)