udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30454__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of udata might be problematic. Click here for more details.

Files changed (413) hide show
  1. tasks/__init__.py +109 -107
  2. tasks/helpers.py +18 -18
  3. udata/__init__.py +4 -4
  4. udata/admin/views.py +5 -5
  5. udata/api/__init__.py +111 -134
  6. udata/api/commands.py +45 -37
  7. udata/api/errors.py +5 -4
  8. udata/api/fields.py +23 -21
  9. udata/api/oauth2.py +55 -74
  10. udata/api/parsers.py +15 -15
  11. udata/api/signals.py +1 -1
  12. udata/api_fields.py +137 -89
  13. udata/app.py +58 -55
  14. udata/assets.py +5 -5
  15. udata/auth/__init__.py +37 -26
  16. udata/auth/forms.py +23 -15
  17. udata/auth/helpers.py +1 -1
  18. udata/auth/mails.py +3 -3
  19. udata/auth/password_validation.py +19 -15
  20. udata/auth/views.py +94 -68
  21. udata/commands/__init__.py +71 -69
  22. udata/commands/cache.py +7 -7
  23. udata/commands/db.py +201 -140
  24. udata/commands/dcat.py +36 -30
  25. udata/commands/fixtures.py +100 -84
  26. udata/commands/images.py +21 -20
  27. udata/commands/info.py +17 -20
  28. udata/commands/init.py +10 -10
  29. udata/commands/purge.py +12 -13
  30. udata/commands/serve.py +41 -29
  31. udata/commands/static.py +16 -18
  32. udata/commands/test.py +20 -20
  33. udata/commands/tests/fixtures.py +26 -24
  34. udata/commands/worker.py +31 -33
  35. udata/core/__init__.py +12 -12
  36. udata/core/activity/__init__.py +0 -1
  37. udata/core/activity/api.py +59 -49
  38. udata/core/activity/models.py +28 -26
  39. udata/core/activity/signals.py +1 -1
  40. udata/core/activity/tasks.py +16 -10
  41. udata/core/badges/api.py +6 -6
  42. udata/core/badges/commands.py +14 -13
  43. udata/core/badges/fields.py +8 -5
  44. udata/core/badges/forms.py +7 -4
  45. udata/core/badges/models.py +16 -31
  46. udata/core/badges/permissions.py +1 -3
  47. udata/core/badges/signals.py +2 -2
  48. udata/core/badges/tasks.py +3 -2
  49. udata/core/badges/tests/test_commands.py +10 -10
  50. udata/core/badges/tests/test_model.py +24 -31
  51. udata/core/contact_point/api.py +19 -18
  52. udata/core/contact_point/api_fields.py +21 -14
  53. udata/core/contact_point/factories.py +2 -2
  54. udata/core/contact_point/forms.py +7 -6
  55. udata/core/contact_point/models.py +3 -5
  56. udata/core/dataservices/api.py +26 -21
  57. udata/core/dataservices/factories.py +13 -11
  58. udata/core/dataservices/models.py +35 -40
  59. udata/core/dataservices/permissions.py +4 -4
  60. udata/core/dataservices/rdf.py +40 -17
  61. udata/core/dataservices/tasks.py +4 -3
  62. udata/core/dataset/actions.py +10 -10
  63. udata/core/dataset/activities.py +21 -23
  64. udata/core/dataset/api.py +321 -298
  65. udata/core/dataset/api_fields.py +443 -271
  66. udata/core/dataset/apiv2.py +305 -229
  67. udata/core/dataset/commands.py +38 -36
  68. udata/core/dataset/constants.py +61 -54
  69. udata/core/dataset/csv.py +70 -74
  70. udata/core/dataset/events.py +39 -32
  71. udata/core/dataset/exceptions.py +8 -4
  72. udata/core/dataset/factories.py +57 -65
  73. udata/core/dataset/forms.py +87 -63
  74. udata/core/dataset/models.py +336 -280
  75. udata/core/dataset/permissions.py +9 -6
  76. udata/core/dataset/preview.py +15 -17
  77. udata/core/dataset/rdf.py +156 -122
  78. udata/core/dataset/search.py +92 -77
  79. udata/core/dataset/signals.py +1 -1
  80. udata/core/dataset/tasks.py +63 -54
  81. udata/core/discussions/actions.py +5 -5
  82. udata/core/discussions/api.py +124 -120
  83. udata/core/discussions/factories.py +2 -2
  84. udata/core/discussions/forms.py +9 -7
  85. udata/core/discussions/metrics.py +1 -3
  86. udata/core/discussions/models.py +25 -24
  87. udata/core/discussions/notifications.py +18 -14
  88. udata/core/discussions/permissions.py +3 -3
  89. udata/core/discussions/signals.py +4 -4
  90. udata/core/discussions/tasks.py +24 -28
  91. udata/core/followers/api.py +32 -33
  92. udata/core/followers/models.py +9 -9
  93. udata/core/followers/signals.py +3 -3
  94. udata/core/jobs/actions.py +7 -7
  95. udata/core/jobs/api.py +99 -92
  96. udata/core/jobs/commands.py +48 -49
  97. udata/core/jobs/forms.py +11 -11
  98. udata/core/jobs/models.py +6 -6
  99. udata/core/metrics/__init__.py +2 -2
  100. udata/core/metrics/commands.py +34 -30
  101. udata/core/metrics/models.py +2 -4
  102. udata/core/metrics/signals.py +1 -1
  103. udata/core/metrics/tasks.py +3 -3
  104. udata/core/organization/activities.py +12 -15
  105. udata/core/organization/api.py +167 -174
  106. udata/core/organization/api_fields.py +183 -124
  107. udata/core/organization/apiv2.py +32 -32
  108. udata/core/organization/commands.py +20 -22
  109. udata/core/organization/constants.py +11 -11
  110. udata/core/organization/csv.py +17 -15
  111. udata/core/organization/factories.py +8 -11
  112. udata/core/organization/forms.py +32 -26
  113. udata/core/organization/metrics.py +2 -1
  114. udata/core/organization/models.py +87 -67
  115. udata/core/organization/notifications.py +18 -14
  116. udata/core/organization/permissions.py +10 -11
  117. udata/core/organization/rdf.py +14 -14
  118. udata/core/organization/search.py +30 -28
  119. udata/core/organization/signals.py +7 -7
  120. udata/core/organization/tasks.py +42 -61
  121. udata/core/owned.py +38 -27
  122. udata/core/post/api.py +82 -81
  123. udata/core/post/constants.py +8 -5
  124. udata/core/post/factories.py +4 -4
  125. udata/core/post/forms.py +13 -14
  126. udata/core/post/models.py +20 -22
  127. udata/core/post/tests/test_api.py +30 -32
  128. udata/core/reports/api.py +8 -7
  129. udata/core/reports/constants.py +1 -3
  130. udata/core/reports/models.py +10 -10
  131. udata/core/reuse/activities.py +15 -19
  132. udata/core/reuse/api.py +123 -126
  133. udata/core/reuse/api_fields.py +120 -85
  134. udata/core/reuse/apiv2.py +11 -10
  135. udata/core/reuse/constants.py +23 -23
  136. udata/core/reuse/csv.py +18 -18
  137. udata/core/reuse/factories.py +5 -9
  138. udata/core/reuse/forms.py +24 -21
  139. udata/core/reuse/models.py +55 -51
  140. udata/core/reuse/permissions.py +2 -2
  141. udata/core/reuse/search.py +49 -46
  142. udata/core/reuse/signals.py +1 -1
  143. udata/core/reuse/tasks.py +4 -5
  144. udata/core/site/api.py +47 -50
  145. udata/core/site/factories.py +2 -2
  146. udata/core/site/forms.py +4 -5
  147. udata/core/site/models.py +94 -63
  148. udata/core/site/rdf.py +14 -14
  149. udata/core/spam/api.py +16 -9
  150. udata/core/spam/constants.py +4 -4
  151. udata/core/spam/fields.py +13 -7
  152. udata/core/spam/models.py +27 -20
  153. udata/core/spam/signals.py +1 -1
  154. udata/core/spam/tests/test_spam.py +6 -5
  155. udata/core/spatial/api.py +72 -80
  156. udata/core/spatial/api_fields.py +73 -58
  157. udata/core/spatial/commands.py +67 -64
  158. udata/core/spatial/constants.py +3 -3
  159. udata/core/spatial/factories.py +37 -54
  160. udata/core/spatial/forms.py +27 -26
  161. udata/core/spatial/geoids.py +17 -17
  162. udata/core/spatial/models.py +43 -47
  163. udata/core/spatial/tasks.py +2 -1
  164. udata/core/spatial/tests/test_api.py +115 -130
  165. udata/core/spatial/tests/test_fields.py +74 -77
  166. udata/core/spatial/tests/test_geoid.py +22 -22
  167. udata/core/spatial/tests/test_models.py +5 -7
  168. udata/core/spatial/translations.py +16 -16
  169. udata/core/storages/__init__.py +16 -18
  170. udata/core/storages/api.py +66 -64
  171. udata/core/storages/tasks.py +7 -7
  172. udata/core/storages/utils.py +15 -15
  173. udata/core/storages/views.py +5 -6
  174. udata/core/tags/api.py +17 -14
  175. udata/core/tags/csv.py +4 -4
  176. udata/core/tags/models.py +8 -5
  177. udata/core/tags/tasks.py +11 -13
  178. udata/core/tags/views.py +4 -4
  179. udata/core/topic/api.py +84 -73
  180. udata/core/topic/apiv2.py +157 -127
  181. udata/core/topic/factories.py +3 -4
  182. udata/core/topic/forms.py +12 -14
  183. udata/core/topic/models.py +14 -19
  184. udata/core/topic/parsers.py +26 -26
  185. udata/core/user/activities.py +30 -29
  186. udata/core/user/api.py +151 -152
  187. udata/core/user/api_fields.py +132 -100
  188. udata/core/user/apiv2.py +7 -7
  189. udata/core/user/commands.py +38 -38
  190. udata/core/user/factories.py +8 -9
  191. udata/core/user/forms.py +14 -11
  192. udata/core/user/metrics.py +2 -2
  193. udata/core/user/models.py +68 -69
  194. udata/core/user/permissions.py +4 -5
  195. udata/core/user/rdf.py +7 -8
  196. udata/core/user/tasks.py +2 -2
  197. udata/core/user/tests/test_user_model.py +24 -16
  198. udata/cors.py +99 -0
  199. udata/db/tasks.py +2 -1
  200. udata/entrypoints.py +35 -31
  201. udata/errors.py +2 -1
  202. udata/event/values.py +6 -6
  203. udata/factories.py +2 -2
  204. udata/features/identicon/api.py +5 -6
  205. udata/features/identicon/backends.py +48 -55
  206. udata/features/identicon/tests/test_backends.py +4 -5
  207. udata/features/notifications/__init__.py +0 -1
  208. udata/features/notifications/actions.py +9 -9
  209. udata/features/notifications/api.py +17 -13
  210. udata/features/territories/__init__.py +12 -10
  211. udata/features/territories/api.py +14 -15
  212. udata/features/territories/models.py +23 -28
  213. udata/features/transfer/actions.py +8 -11
  214. udata/features/transfer/api.py +84 -77
  215. udata/features/transfer/factories.py +2 -1
  216. udata/features/transfer/models.py +11 -12
  217. udata/features/transfer/notifications.py +19 -15
  218. udata/features/transfer/permissions.py +5 -5
  219. udata/forms/__init__.py +5 -2
  220. udata/forms/fields.py +164 -172
  221. udata/forms/validators.py +19 -22
  222. udata/forms/widgets.py +9 -13
  223. udata/frontend/__init__.py +31 -26
  224. udata/frontend/csv.py +68 -58
  225. udata/frontend/markdown.py +40 -44
  226. udata/harvest/actions.py +89 -77
  227. udata/harvest/api.py +294 -238
  228. udata/harvest/backends/__init__.py +4 -4
  229. udata/harvest/backends/base.py +128 -111
  230. udata/harvest/backends/dcat.py +80 -66
  231. udata/harvest/commands.py +56 -60
  232. udata/harvest/csv.py +8 -8
  233. udata/harvest/exceptions.py +6 -3
  234. udata/harvest/filters.py +24 -23
  235. udata/harvest/forms.py +27 -28
  236. udata/harvest/models.py +88 -80
  237. udata/harvest/notifications.py +15 -10
  238. udata/harvest/signals.py +13 -13
  239. udata/harvest/tasks.py +11 -10
  240. udata/harvest/tests/factories.py +23 -24
  241. udata/harvest/tests/test_actions.py +136 -166
  242. udata/harvest/tests/test_api.py +220 -214
  243. udata/harvest/tests/test_base_backend.py +117 -112
  244. udata/harvest/tests/test_dcat_backend.py +380 -308
  245. udata/harvest/tests/test_filters.py +33 -22
  246. udata/harvest/tests/test_models.py +11 -14
  247. udata/harvest/tests/test_notifications.py +6 -7
  248. udata/harvest/tests/test_tasks.py +7 -6
  249. udata/i18n.py +237 -78
  250. udata/linkchecker/backends.py +5 -11
  251. udata/linkchecker/checker.py +23 -22
  252. udata/linkchecker/commands.py +4 -6
  253. udata/linkchecker/models.py +6 -6
  254. udata/linkchecker/tasks.py +18 -20
  255. udata/mail.py +21 -21
  256. udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
  257. udata/migrations/2020-08-24-add-fs-filename.py +9 -8
  258. udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
  259. udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
  260. udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
  261. udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
  262. udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
  263. udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
  264. udata/migrations/2021-08-17-follow-integrity.py +5 -4
  265. udata/migrations/2021-08-17-harvest-integrity.py +13 -12
  266. udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
  267. udata/migrations/2021-08-17-transfer-integrity.py +5 -4
  268. udata/migrations/2021-08-17-users-integrity.py +9 -8
  269. udata/migrations/2021-12-14-reuse-topics.py +7 -6
  270. udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
  271. udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
  272. udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
  273. udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
  274. udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
  275. udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
  276. udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
  277. udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
  278. udata/migrations/__init__.py +123 -105
  279. udata/models/__init__.py +4 -4
  280. udata/mongo/__init__.py +13 -11
  281. udata/mongo/badges_field.py +3 -2
  282. udata/mongo/datetime_fields.py +13 -12
  283. udata/mongo/document.py +17 -16
  284. udata/mongo/engine.py +15 -16
  285. udata/mongo/errors.py +2 -1
  286. udata/mongo/extras_fields.py +30 -20
  287. udata/mongo/queryset.py +12 -12
  288. udata/mongo/slug_fields.py +38 -28
  289. udata/mongo/taglist_field.py +1 -2
  290. udata/mongo/url_field.py +5 -5
  291. udata/mongo/uuid_fields.py +4 -3
  292. udata/notifications/__init__.py +1 -1
  293. udata/notifications/mattermost.py +10 -9
  294. udata/rdf.py +167 -188
  295. udata/routing.py +40 -45
  296. udata/search/__init__.py +18 -19
  297. udata/search/adapter.py +17 -16
  298. udata/search/commands.py +44 -51
  299. udata/search/fields.py +13 -20
  300. udata/search/query.py +23 -18
  301. udata/search/result.py +9 -10
  302. udata/sentry.py +21 -19
  303. udata/settings.py +262 -198
  304. udata/sitemap.py +8 -6
  305. udata/storage/s3.py +20 -13
  306. udata/tags.py +4 -5
  307. udata/tasks.py +43 -42
  308. udata/tests/__init__.py +9 -6
  309. udata/tests/api/__init__.py +8 -6
  310. udata/tests/api/test_auth_api.py +395 -321
  311. udata/tests/api/test_base_api.py +33 -35
  312. udata/tests/api/test_contact_points.py +7 -9
  313. udata/tests/api/test_dataservices_api.py +211 -158
  314. udata/tests/api/test_datasets_api.py +823 -812
  315. udata/tests/api/test_follow_api.py +13 -15
  316. udata/tests/api/test_me_api.py +95 -112
  317. udata/tests/api/test_organizations_api.py +301 -339
  318. udata/tests/api/test_reports_api.py +35 -25
  319. udata/tests/api/test_reuses_api.py +134 -139
  320. udata/tests/api/test_swagger.py +5 -5
  321. udata/tests/api/test_tags_api.py +18 -25
  322. udata/tests/api/test_topics_api.py +94 -94
  323. udata/tests/api/test_transfer_api.py +53 -48
  324. udata/tests/api/test_user_api.py +128 -141
  325. udata/tests/apiv2/test_datasets.py +290 -198
  326. udata/tests/apiv2/test_me_api.py +10 -11
  327. udata/tests/apiv2/test_organizations.py +56 -74
  328. udata/tests/apiv2/test_swagger.py +5 -5
  329. udata/tests/apiv2/test_topics.py +69 -87
  330. udata/tests/cli/test_cli_base.py +8 -8
  331. udata/tests/cli/test_db_cli.py +21 -19
  332. udata/tests/dataservice/test_dataservice_tasks.py +8 -12
  333. udata/tests/dataset/test_csv_adapter.py +44 -35
  334. udata/tests/dataset/test_dataset_actions.py +2 -3
  335. udata/tests/dataset/test_dataset_commands.py +7 -8
  336. udata/tests/dataset/test_dataset_events.py +36 -29
  337. udata/tests/dataset/test_dataset_model.py +224 -217
  338. udata/tests/dataset/test_dataset_rdf.py +142 -131
  339. udata/tests/dataset/test_dataset_tasks.py +15 -15
  340. udata/tests/dataset/test_resource_preview.py +10 -13
  341. udata/tests/features/territories/__init__.py +9 -13
  342. udata/tests/features/territories/test_territories_api.py +71 -91
  343. udata/tests/forms/test_basic_fields.py +7 -7
  344. udata/tests/forms/test_current_user_field.py +39 -66
  345. udata/tests/forms/test_daterange_field.py +31 -39
  346. udata/tests/forms/test_dict_field.py +28 -26
  347. udata/tests/forms/test_extras_fields.py +102 -76
  348. udata/tests/forms/test_form_field.py +8 -8
  349. udata/tests/forms/test_image_field.py +33 -26
  350. udata/tests/forms/test_model_field.py +134 -123
  351. udata/tests/forms/test_model_list_field.py +7 -7
  352. udata/tests/forms/test_nested_model_list_field.py +117 -79
  353. udata/tests/forms/test_publish_as_field.py +36 -65
  354. udata/tests/forms/test_reference_field.py +34 -53
  355. udata/tests/forms/test_user_forms.py +23 -21
  356. udata/tests/forms/test_uuid_field.py +6 -10
  357. udata/tests/frontend/__init__.py +9 -6
  358. udata/tests/frontend/test_auth.py +7 -6
  359. udata/tests/frontend/test_csv.py +81 -96
  360. udata/tests/frontend/test_hooks.py +43 -43
  361. udata/tests/frontend/test_markdown.py +211 -191
  362. udata/tests/helpers.py +32 -37
  363. udata/tests/models.py +2 -2
  364. udata/tests/organization/test_csv_adapter.py +21 -16
  365. udata/tests/organization/test_notifications.py +11 -18
  366. udata/tests/organization/test_organization_model.py +13 -13
  367. udata/tests/organization/test_organization_rdf.py +29 -22
  368. udata/tests/organization/test_organization_tasks.py +16 -17
  369. udata/tests/plugin.py +79 -73
  370. udata/tests/reuse/test_reuse_model.py +21 -21
  371. udata/tests/reuse/test_reuse_task.py +11 -13
  372. udata/tests/search/__init__.py +11 -12
  373. udata/tests/search/test_adapter.py +60 -70
  374. udata/tests/search/test_query.py +16 -16
  375. udata/tests/search/test_results.py +10 -7
  376. udata/tests/site/test_site_api.py +11 -16
  377. udata/tests/site/test_site_metrics.py +20 -30
  378. udata/tests/site/test_site_model.py +4 -5
  379. udata/tests/site/test_site_rdf.py +94 -78
  380. udata/tests/test_activity.py +17 -17
  381. udata/tests/test_cors.py +62 -0
  382. udata/tests/test_discussions.py +292 -299
  383. udata/tests/test_i18n.py +37 -40
  384. udata/tests/test_linkchecker.py +91 -85
  385. udata/tests/test_mail.py +13 -17
  386. udata/tests/test_migrations.py +219 -180
  387. udata/tests/test_model.py +164 -157
  388. udata/tests/test_notifications.py +17 -17
  389. udata/tests/test_owned.py +14 -14
  390. udata/tests/test_rdf.py +25 -23
  391. udata/tests/test_routing.py +89 -93
  392. udata/tests/test_storages.py +137 -128
  393. udata/tests/test_tags.py +44 -46
  394. udata/tests/test_topics.py +7 -7
  395. udata/tests/test_transfer.py +42 -49
  396. udata/tests/test_uris.py +160 -161
  397. udata/tests/test_utils.py +79 -71
  398. udata/tests/user/test_user_rdf.py +5 -9
  399. udata/tests/workers/test_jobs_commands.py +57 -58
  400. udata/tests/workers/test_tasks_routing.py +23 -29
  401. udata/tests/workers/test_workers_api.py +125 -131
  402. udata/tests/workers/test_workers_helpers.py +6 -6
  403. udata/tracking.py +4 -6
  404. udata/uris.py +45 -46
  405. udata/utils.py +68 -66
  406. udata/wsgi.py +1 -1
  407. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/METADATA +7 -3
  408. udata-9.1.2.dev30454.dist-info/RECORD +706 -0
  409. udata-9.1.2.dev30355.dist-info/RECORD +0 -704
  410. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/LICENSE +0 -0
  411. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/WHEEL +0 -0
  412. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/entry_points.txt +0 -0
  413. {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/top_level.txt +0 -0
@@ -1,142 +1,201 @@
1
1
  from flask import request
2
2
 
3
- from udata.api import api, fields, base_reference
3
+ from udata.api import api, base_reference, fields
4
4
  from udata.core.badges.fields import badge_fields
5
5
  from udata.core.organization.permissions import OrganizationPrivatePermission
6
6
 
7
- from .constants import ORG_ROLES, DEFAULT_ROLE, MEMBERSHIP_STATUS, BIGGEST_LOGO_SIZE
8
-
9
- org_ref_fields = api.inherit('OrganizationReference', base_reference, {
10
- 'name': fields.String(description='The organization name', readonly=True),
11
- 'acronym': fields.String(description='The organization acronym'),
12
- 'uri': fields.UrlFor(
13
- 'api.organization', lambda o: {'org': o},
14
- description='The organization API URI', readonly=True),
15
- 'slug': fields.String(
16
- description='The organization string used as permalink',
17
- required=True),
18
- 'page': fields.UrlFor(
19
- 'organizations.show', lambda o: {'org': o},
20
- description='The organization web page URL', readonly=True, fallback_endpoint='api.organization'),
21
- 'logo': fields.ImageField(original=True,
22
- description='The organization logo URL'),
23
- 'logo_thumbnail': fields.ImageField(attribute='logo', size=BIGGEST_LOGO_SIZE,
24
- description='The organization logo thumbnail URL. This is the square '
25
- '({0}x{0}) and cropped version.'.format(BIGGEST_LOGO_SIZE)),
26
- 'badges': fields.List(fields.Nested(badge_fields),
27
- description='The organization badges',
28
- readonly=True),
29
- })
7
+ from .constants import BIGGEST_LOGO_SIZE, DEFAULT_ROLE, MEMBERSHIP_STATUS, ORG_ROLES
8
+
9
+ org_ref_fields = api.inherit(
10
+ "OrganizationReference",
11
+ base_reference,
12
+ {
13
+ "name": fields.String(description="The organization name", readonly=True),
14
+ "acronym": fields.String(description="The organization acronym"),
15
+ "uri": fields.UrlFor(
16
+ "api.organization",
17
+ lambda o: {"org": o},
18
+ description="The organization API URI",
19
+ readonly=True,
20
+ ),
21
+ "slug": fields.String(
22
+ description="The organization string used as permalink", required=True
23
+ ),
24
+ "page": fields.UrlFor(
25
+ "organizations.show",
26
+ lambda o: {"org": o},
27
+ description="The organization web page URL",
28
+ readonly=True,
29
+ fallback_endpoint="api.organization",
30
+ ),
31
+ "logo": fields.ImageField(original=True, description="The organization logo URL"),
32
+ "logo_thumbnail": fields.ImageField(
33
+ attribute="logo",
34
+ size=BIGGEST_LOGO_SIZE,
35
+ description="The organization logo thumbnail URL. This is the square "
36
+ "({0}x{0}) and cropped version.".format(BIGGEST_LOGO_SIZE),
37
+ ),
38
+ "badges": fields.List(
39
+ fields.Nested(badge_fields), description="The organization badges", readonly=True
40
+ ),
41
+ },
42
+ )
30
43
 
31
44
  from udata.core.user.api_fields import user_ref_fields # noqa: required
32
45
 
46
+
33
47
  def check_can_access_email():
34
48
  # This endpoint is secure, only organization member has access.
35
- if request.endpoint == 'api.request_membership':
49
+ if request.endpoint == "api.request_membership":
36
50
  return True
37
51
 
38
- if request.endpoint != 'api.organization':
52
+ if request.endpoint != "api.organization":
39
53
  return False
40
54
 
41
- org = request.view_args.get('org')
55
+ org = request.view_args.get("org")
42
56
  if org is None:
43
57
  return False
44
-
58
+
45
59
  return OrganizationPrivatePermission(org).can()
46
60
 
47
- member_user_with_email_fields = api.inherit('MemberUserWithEmail', user_ref_fields, {
48
- 'email': fields.Raw(
49
- attribute=lambda o: o.email if check_can_access_email() else None,
50
- description='The user email (only present on show organization endpoint if the current user has edit permission on the org)', readonly=True),
51
- })
52
-
53
- request_fields = api.model('MembershipRequest', {
54
- 'id': fields.String(readonly=True),
55
- 'user': fields.Nested(member_user_with_email_fields),
56
- 'created': fields.ISODateTime(
57
- description='The request creation date', readonly=True),
58
- 'status': fields.String(
59
- description='The current request status', required=True,
60
- enum=list(MEMBERSHIP_STATUS)),
61
- 'comment': fields.String(
62
- description='A request comment from the user', required=True),
63
- })
64
-
65
- member_fields = api.model('Member', {
66
- 'user': fields.Nested(member_user_with_email_fields),
67
- 'role': fields.String(
68
- description='The member role in the organization', required=True,
69
- enum=list(ORG_ROLES), default=DEFAULT_ROLE),
70
- 'since': fields.ISODateTime(
71
- description='The date the user joined the organization', readonly=True),
72
- })
73
-
74
- org_fields = api.model('Organization', {
75
- 'id': fields.String(
76
- description='The organization identifier', required=True),
77
- 'name': fields.String(description='The organization name', required=True),
78
- 'acronym': fields.String(description='The organization acronym'),
79
- 'url': fields.String(description='The organization website URL'),
80
- 'slug': fields.String(
81
- description='The organization string used as permalink',
82
- required=True),
83
- 'description': fields.Markdown(
84
- description='The organization description in Markdown', required=True),
85
- 'business_number_id': fields.String(description='The organization\'s business identification number.'),
86
- 'created_at': fields.ISODateTime(
87
- description='The organization creation date', readonly=True),
88
- 'last_modified': fields.ISODateTime(
89
- description='The organization last modification date', readonly=True),
90
- 'deleted': fields.ISODateTime(
91
- description='The organization deletion date if deleted',
92
- readonly=True),
93
- 'metrics': fields.Raw(
94
- attribute=lambda o: o.get_metrics(),
95
- description='The organization metrics', readonly=True),
96
- 'uri': fields.UrlFor(
97
- 'api.organization', lambda o: {'org': o},
98
- description='The organization API URI', readonly=True),
99
- 'page': fields.UrlFor(
100
- 'organizations.show', lambda o: {'org': o},
101
- description='The organization page URL', readonly=True, fallback_endpoint='api.organization'),
102
- 'logo': fields.ImageField(original=True,
103
- description='The organization logo URL'),
104
- 'logo_thumbnail': fields.ImageField(attribute='logo', size=BIGGEST_LOGO_SIZE,
105
- description='The organization logo thumbnail URL. This is the square '
106
- '({0}x{0}) and cropped version.'.format(BIGGEST_LOGO_SIZE)),
107
- 'members': fields.List(
108
- fields.Nested(member_fields, description='The organization members')),
109
- 'badges': fields.List(fields.Nested(badge_fields),
110
- description='The organization badges',
111
- readonly=True),
112
- 'extras': fields.Raw(description='Extras attributes as key-value pairs'),
113
- })
114
-
115
- org_page_fields = api.model('OrganizationPage', fields.pager(org_fields))
116
-
117
-
118
- refuse_membership_fields = api.model('RefuseMembership', {
119
- 'comment': fields.String(
120
- description='The refusal comment.'),
121
- })
122
-
123
-
124
- org_role_fields = api.model('OrganizationRole', {
125
- 'id': fields.String(description='The role identifier'),
126
- 'label': fields.String(description='The role label')
127
- })
128
-
129
-
130
- org_suggestion_fields = api.model('OrganizationSuggestion', {
131
- 'id': fields.String(
132
- description='The organization identifier', readonly=True),
133
- 'name': fields.String(description='The organization name', readonly=True),
134
- 'acronym': fields.String(
135
- description='The organization acronym', readonly=True),
136
- 'slug': fields.String(
137
- description='The organization permalink string', readonly=True),
138
- 'image_url': fields.ImageField(size=BIGGEST_LOGO_SIZE, description='The organization logo URL', readonly=True),
139
- 'page': fields.UrlFor(
140
- 'organizations.show_redirect', lambda o: {'org': o['slug']},
141
- description='The organization web page URL', readonly=True, fallback_endpoint='api.organization')
142
- })
61
+
62
+ member_user_with_email_fields = api.inherit(
63
+ "MemberUserWithEmail",
64
+ user_ref_fields,
65
+ {
66
+ "email": fields.Raw(
67
+ attribute=lambda o: o.email if check_can_access_email() else None,
68
+ description="The user email (only present on show organization endpoint if the current user has edit permission on the org)",
69
+ readonly=True,
70
+ ),
71
+ },
72
+ )
73
+
74
+ request_fields = api.model(
75
+ "MembershipRequest",
76
+ {
77
+ "id": fields.String(readonly=True),
78
+ "user": fields.Nested(member_user_with_email_fields),
79
+ "created": fields.ISODateTime(description="The request creation date", readonly=True),
80
+ "status": fields.String(
81
+ description="The current request status", required=True, enum=list(MEMBERSHIP_STATUS)
82
+ ),
83
+ "comment": fields.String(description="A request comment from the user", required=True),
84
+ },
85
+ )
86
+
87
+ member_fields = api.model(
88
+ "Member",
89
+ {
90
+ "user": fields.Nested(member_user_with_email_fields),
91
+ "role": fields.String(
92
+ description="The member role in the organization",
93
+ required=True,
94
+ enum=list(ORG_ROLES),
95
+ default=DEFAULT_ROLE,
96
+ ),
97
+ "since": fields.ISODateTime(
98
+ description="The date the user joined the organization", readonly=True
99
+ ),
100
+ },
101
+ )
102
+
103
+ org_fields = api.model(
104
+ "Organization",
105
+ {
106
+ "id": fields.String(description="The organization identifier", required=True),
107
+ "name": fields.String(description="The organization name", required=True),
108
+ "acronym": fields.String(description="The organization acronym"),
109
+ "url": fields.String(description="The organization website URL"),
110
+ "slug": fields.String(
111
+ description="The organization string used as permalink", required=True
112
+ ),
113
+ "description": fields.Markdown(
114
+ description="The organization description in Markdown", required=True
115
+ ),
116
+ "business_number_id": fields.String(
117
+ description="The organization's business identification number."
118
+ ),
119
+ "created_at": fields.ISODateTime(
120
+ description="The organization creation date", readonly=True
121
+ ),
122
+ "last_modified": fields.ISODateTime(
123
+ description="The organization last modification date", readonly=True
124
+ ),
125
+ "deleted": fields.ISODateTime(
126
+ description="The organization deletion date if deleted", readonly=True
127
+ ),
128
+ "metrics": fields.Raw(
129
+ attribute=lambda o: o.get_metrics(),
130
+ description="The organization metrics",
131
+ readonly=True,
132
+ ),
133
+ "uri": fields.UrlFor(
134
+ "api.organization",
135
+ lambda o: {"org": o},
136
+ description="The organization API URI",
137
+ readonly=True,
138
+ ),
139
+ "page": fields.UrlFor(
140
+ "organizations.show",
141
+ lambda o: {"org": o},
142
+ description="The organization page URL",
143
+ readonly=True,
144
+ fallback_endpoint="api.organization",
145
+ ),
146
+ "logo": fields.ImageField(original=True, description="The organization logo URL"),
147
+ "logo_thumbnail": fields.ImageField(
148
+ attribute="logo",
149
+ size=BIGGEST_LOGO_SIZE,
150
+ description="The organization logo thumbnail URL. This is the square "
151
+ "({0}x{0}) and cropped version.".format(BIGGEST_LOGO_SIZE),
152
+ ),
153
+ "members": fields.List(
154
+ fields.Nested(member_fields, description="The organization members")
155
+ ),
156
+ "badges": fields.List(
157
+ fields.Nested(badge_fields), description="The organization badges", readonly=True
158
+ ),
159
+ "extras": fields.Raw(description="Extras attributes as key-value pairs"),
160
+ },
161
+ )
162
+
163
+ org_page_fields = api.model("OrganizationPage", fields.pager(org_fields))
164
+
165
+
166
+ refuse_membership_fields = api.model(
167
+ "RefuseMembership",
168
+ {
169
+ "comment": fields.String(description="The refusal comment."),
170
+ },
171
+ )
172
+
173
+
174
+ org_role_fields = api.model(
175
+ "OrganizationRole",
176
+ {
177
+ "id": fields.String(description="The role identifier"),
178
+ "label": fields.String(description="The role label"),
179
+ },
180
+ )
181
+
182
+
183
+ org_suggestion_fields = api.model(
184
+ "OrganizationSuggestion",
185
+ {
186
+ "id": fields.String(description="The organization identifier", readonly=True),
187
+ "name": fields.String(description="The organization name", readonly=True),
188
+ "acronym": fields.String(description="The organization acronym", readonly=True),
189
+ "slug": fields.String(description="The organization permalink string", readonly=True),
190
+ "image_url": fields.ImageField(
191
+ size=BIGGEST_LOGO_SIZE, description="The organization logo URL", readonly=True
192
+ ),
193
+ "page": fields.UrlFor(
194
+ "organizations.show_redirect",
195
+ lambda o: {"org": o["slug"]},
196
+ description="The organization web page URL",
197
+ readonly=True,
198
+ fallback_endpoint="api.organization",
199
+ ),
200
+ },
201
+ )
@@ -2,61 +2,61 @@ import mongoengine
2
2
  from flask import request
3
3
 
4
4
  from udata import search
5
- from udata.api import apiv2, API
6
- from udata.utils import multi_to_dict
5
+ from udata.api import API, apiv2
7
6
  from udata.core.contact_point.api_fields import contact_point_fields
7
+ from udata.utils import multi_to_dict
8
+
9
+ from .api_fields import member_fields, org_fields, org_page_fields
10
+ from .permissions import EditOrganizationPermission, OrganizationPrivatePermission
8
11
  from .search import OrganizationSearch
9
- from .api_fields import org_page_fields, org_fields, member_fields
10
- from .permissions import (
11
- EditOrganizationPermission, OrganizationPrivatePermission
12
- )
13
12
 
14
- apiv2.inherit('OrganizationPage', org_page_fields)
15
- apiv2.inherit('Organization', org_fields)
16
- apiv2.inherit('Member', member_fields)
17
- apiv2.inherit('ContactPoint', contact_point_fields)
13
+ apiv2.inherit("OrganizationPage", org_page_fields)
14
+ apiv2.inherit("Organization", org_fields)
15
+ apiv2.inherit("Member", member_fields)
16
+ apiv2.inherit("ContactPoint", contact_point_fields)
18
17
 
19
18
 
20
- ns = apiv2.namespace('organizations', 'Organization related operations')
19
+ ns = apiv2.namespace("organizations", "Organization related operations")
21
20
  search_parser = OrganizationSearch.as_request_parser()
22
21
 
23
- DEFAULT_SORTING = '-created_at'
22
+ DEFAULT_SORTING = "-created_at"
24
23
 
25
24
 
26
- @ns.route('/search/', endpoint='organization_search')
25
+ @ns.route("/search/", endpoint="organization_search")
27
26
  class OrganizationSearchAPI(API):
28
- '''Organizations collection search endpoint'''
29
- @apiv2.doc('search_organizations')
27
+ """Organizations collection search endpoint"""
28
+
29
+ @apiv2.doc("search_organizations")
30
30
  @apiv2.expect(search_parser)
31
31
  @apiv2.marshal_with(org_page_fields)
32
32
  def get(self):
33
- '''Search all organizations'''
33
+ """Search all organizations"""
34
34
  search_parser.parse_args()
35
35
  return search.query(OrganizationSearch, **multi_to_dict(request.args))
36
36
 
37
37
 
38
- @ns.route('/<org:org>/extras/', endpoint='organization_extras')
39
- @apiv2.response(400, 'Wrong payload format, dict expected')
40
- @apiv2.response(400, 'Wrong payload format, list expected')
41
- @apiv2.response(404, 'Organization not found')
42
- @apiv2.response(410, 'Organization has been deleted')
38
+ @ns.route("/<org:org>/extras/", endpoint="organization_extras")
39
+ @apiv2.response(400, "Wrong payload format, dict expected")
40
+ @apiv2.response(400, "Wrong payload format, list expected")
41
+ @apiv2.response(404, "Organization not found")
42
+ @apiv2.response(410, "Organization has been deleted")
43
43
  class OrganizationExtrasAPI(API):
44
- @apiv2.doc('get_organization_extras')
44
+ @apiv2.doc("get_organization_extras")
45
45
  def get(self, org):
46
- '''Get an organization extras given its identifier'''
46
+ """Get an organization extras given its identifier"""
47
47
  if org.deleted:
48
- apiv2.abort(410, 'Organization has been deleted')
48
+ apiv2.abort(410, "Organization has been deleted")
49
49
  return org.extras
50
50
 
51
51
  @apiv2.secure
52
- @apiv2.doc('update_organization_extras')
52
+ @apiv2.doc("update_organization_extras")
53
53
  def put(self, org):
54
- '''Update a given organization extras'''
54
+ """Update a given organization extras"""
55
55
  data = request.json
56
56
  if not isinstance(data, dict):
57
- apiv2.abort(400, 'Wrong payload format, dict expected')
57
+ apiv2.abort(400, "Wrong payload format, dict expected")
58
58
  if org.deleted:
59
- apiv2.abort(410, 'Organization has been deleted')
59
+ apiv2.abort(410, "Organization has been deleted")
60
60
  EditOrganizationPermission(org).test()
61
61
  # first remove extras key associated to a None value in payload
62
62
  for key in [k for k in data if data[k] is None]:
@@ -72,14 +72,14 @@ class OrganizationExtrasAPI(API):
72
72
  return org.extras
73
73
 
74
74
  @apiv2.secure
75
- @apiv2.doc('delete_organization_extras')
75
+ @apiv2.doc("delete_organization_extras")
76
76
  def delete(self, org):
77
- '''Delete a given organization extras key on a given organization'''
77
+ """Delete a given organization extras key on a given organization"""
78
78
  data = request.json
79
79
  if not isinstance(data, list):
80
- apiv2.abort(400, 'Wrong payload format, list expected')
80
+ apiv2.abort(400, "Wrong payload format, list expected")
81
81
  if org.deleted:
82
- apiv2.abort(410, 'Organization has been deleted')
82
+ apiv2.abort(410, "Organization has been deleted")
83
83
  EditOrganizationPermission(org).test()
84
84
  for key in data:
85
85
  try:
@@ -8,43 +8,41 @@ from udata.models import GeoZone, Organization
8
8
  log = logging.getLogger(__name__)
9
9
 
10
10
 
11
- @cli.group('organizations')
11
+ @cli.group("organizations")
12
12
  def grp():
13
- '''Organizations related operations'''
13
+ """Organizations related operations"""
14
14
  pass
15
15
 
16
16
 
17
17
  @grp.command()
18
- @click.argument('geoid', metavar='<geoid>')
19
- @click.argument('organization_id_or_slug', metavar='<organization>')
18
+ @click.argument("geoid", metavar="<geoid>")
19
+ @click.argument("organization_id_or_slug", metavar="<organization>")
20
20
  def attach_zone(geoid, organization_id_or_slug):
21
- '''Attach a zone <geoid> restricted to level for a given <organization>.'''
22
- organization = Organization.objects.get_by_id_or_slug(
23
- organization_id_or_slug)
21
+ """Attach a zone <geoid> restricted to level for a given <organization>."""
22
+ organization = Organization.objects.get_by_id_or_slug(organization_id_or_slug)
24
23
  if not organization:
25
- log.error('No organization found for %s', organization_id_or_slug)
24
+ log.error("No organization found for %s", organization_id_or_slug)
26
25
  geozone = GeoZone.objects.get(id=geoid)
27
26
  if not geozone:
28
- log.error('No geozone found for %s', geoid)
29
- log.info('Attaching {organization} with {geozone.name}'.format(
30
- organization=organization, geozone=geozone))
27
+ log.error("No geozone found for %s", geoid)
28
+ log.info(
29
+ "Attaching {organization} with {geozone.name}".format(
30
+ organization=organization, geozone=geozone
31
+ )
32
+ )
31
33
  organization.zone = geozone.id
32
34
  organization.save()
33
- log.info('Done')
35
+ log.info("Done")
34
36
 
35
37
 
36
38
  @grp.command()
37
- @click.argument('organization_id_or_slug', metavar='<organization>')
39
+ @click.argument("organization_id_or_slug", metavar="<organization>")
38
40
  def detach_zone(organization_id_or_slug):
39
- '''Detach the zone of a given <organization>.'''
40
- organization = Organization.objects.get_by_id_or_slug(
41
- organization_id_or_slug)
41
+ """Detach the zone of a given <organization>."""
42
+ organization = Organization.objects.get_by_id_or_slug(organization_id_or_slug)
42
43
  if not organization:
43
- exit_with_error(
44
- 'No organization found for {0}'.format(organization_id_or_slug)
45
- )
46
- log.info('Detaching {organization} from {organization.zone}'.format(
47
- organization=organization))
44
+ exit_with_error("No organization found for {0}".format(organization_id_or_slug))
45
+ log.info("Detaching {organization} from {organization.zone}".format(organization=organization))
48
46
  organization.zone = None
49
47
  organization.save()
50
- log.info('Done')
48
+ log.info("Done")
@@ -1,27 +1,27 @@
1
1
  from udata.i18n import lazy_gettext as _
2
2
 
3
3
  ORG_ROLES = {
4
- 'admin': _('Administrator'),
5
- 'editor': _('Editor'),
4
+ "admin": _("Administrator"),
5
+ "editor": _("Editor"),
6
6
  }
7
- DEFAULT_ROLE = 'editor'
7
+ DEFAULT_ROLE = "editor"
8
8
 
9
9
 
10
10
  MEMBERSHIP_STATUS = {
11
- 'pending': _('Pending'),
12
- 'accepted': _('Accepted'),
13
- 'refused': _('Refused'),
11
+ "pending": _("Pending"),
12
+ "accepted": _("Accepted"),
13
+ "refused": _("Refused"),
14
14
  }
15
15
 
16
16
  LOGO_MAX_SIZE = 500
17
17
  LOGO_SIZES = [100, 60, 25]
18
18
  BIGGEST_LOGO_SIZE = LOGO_SIZES[0]
19
19
 
20
- PUBLIC_SERVICE = 'public-service'
21
- CERTIFIED = 'certified'
22
- ASSOCIATION = 'Association'
23
- COMPANY = 'Company'
24
- LOCAL_AUTHORITY = 'Local authority'
20
+ PUBLIC_SERVICE = "public-service"
21
+ CERTIFIED = "certified"
22
+ ASSOCIATION = "Association"
23
+ COMPANY = "Company"
24
+ LOCAL_AUTHORITY = "Local authority"
25
25
 
26
26
  TITLE_SIZE_LIMIT = 350
27
27
  DESCRIPTION_SIZE_LIMIT = 100000
@@ -9,17 +9,17 @@ class OrganizationCsvAdapter(csv.Adapter):
9
9
  downloads_counts = None
10
10
 
11
11
  fields = (
12
- 'id',
13
- 'name',
14
- 'slug',
15
- ('url', 'external_url'),
16
- 'description',
17
- ('logo', lambda o: o.logo(external=True)),
18
- ('badges', lambda o: ','.join([badge.kind for badge in o.badges])),
19
- 'created_at',
20
- 'last_modified',
21
- 'business_number_id',
22
- ('members_count', lambda o: len(o.members)),
12
+ "id",
13
+ "name",
14
+ "slug",
15
+ ("url", "external_url"),
16
+ "description",
17
+ ("logo", lambda o: o.logo(external=True)),
18
+ ("badges", lambda o: ",".join([badge.kind for badge in o.badges])),
19
+ "created_at",
20
+ "last_modified",
21
+ "business_number_id",
22
+ ("members_count", lambda o: len(o.members)),
23
23
  )
24
24
 
25
25
  def dynamic_fields(self):
@@ -27,12 +27,12 @@ class OrganizationCsvAdapter(csv.Adapter):
27
27
 
28
28
  def get_dynamic_field_downloads(self):
29
29
  downloads_counts = self.get_downloads_counts()
30
- return [('downloads', lambda o: downloads_counts.get(str(o.id), 0))]
30
+ return [("downloads", lambda o: downloads_counts.get(str(o.id), 0))]
31
31
 
32
32
  def get_downloads_counts(self):
33
- '''
33
+ """
34
34
  Prefetch all the resources' downloads for all selected organization into memory
35
- '''
35
+ """
36
36
  if self.downloads_counts is not None:
37
37
  return self.downloads_counts
38
38
 
@@ -44,6 +44,8 @@ class OrganizationCsvAdapter(csv.Adapter):
44
44
  if self.downloads_counts.get(org_id) is None:
45
45
  self.downloads_counts[org_id] = 0
46
46
 
47
- self.downloads_counts[org_id] += sum(resource.metrics.get('views', 0) for resource in dataset.resources)
47
+ self.downloads_counts[org_id] += sum(
48
+ resource.metrics.get("views", 0) for resource in dataset.resources
49
+ )
48
50
 
49
51
  return self.downloads_counts
@@ -2,22 +2,19 @@ import factory
2
2
 
3
3
  from udata.factories import ModelFactory
4
4
 
5
- from .models import Organization, Team, Member
5
+ from .models import Member, Organization, Team
6
6
 
7
7
 
8
8
  class OrganizationFactory(ModelFactory):
9
9
  class Meta:
10
10
  model = Organization
11
11
 
12
- name = factory.Faker('sentence')
13
- description = factory.Faker('text')
14
- members = factory.LazyAttribute(lambda o: [
15
- Member(user=user, role='admin')
16
- for user in o.admins
17
- ] + [
18
- Member(user=user, role='editor')
19
- for user in o.editors
20
- ])
12
+ name = factory.Faker("sentence")
13
+ description = factory.Faker("text")
14
+ members = factory.LazyAttribute(
15
+ lambda o: [Member(user=user, role="admin") for user in o.admins]
16
+ + [Member(user=user, role="editor") for user in o.editors]
17
+ )
21
18
 
22
19
  class Params:
23
20
  admins = []
@@ -28,4 +25,4 @@ class TeamFactory(ModelFactory):
28
25
  class Meta:
29
26
  model = Team
30
27
 
31
- name = factory.Faker('sentence')
28
+ name = factory.Faker("sentence")