fairdm 0.3.0__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.
Files changed (572) hide show
  1. demo/README.md +466 -0
  2. demo/__init__.py +1 -0
  3. demo/admin.py +519 -0
  4. demo/apps.py +22 -0
  5. demo/config.py +460 -0
  6. demo/factories.py +374 -0
  7. demo/filters.py +475 -0
  8. demo/forms.py +584 -0
  9. demo/management/__init__.py +0 -0
  10. demo/management/commands/__init__.py +0 -0
  11. demo/management/commands/seed_contributors.py +42 -0
  12. demo/management/commands/seed_overviews.py +39 -0
  13. demo/management/commands/seed_profiles.py +503 -0
  14. demo/management/commands/seed_record_search.py +40 -0
  15. demo/migrations/0001_initial.py +724 -0
  16. demo/migrations/0002_alter_customparentsample_options.py +17 -0
  17. demo/migrations/0003_icp_ms_measurement_uncertainty_and_more.py +24 -0
  18. demo/migrations/__init__.py +0 -0
  19. demo/models.py +690 -0
  20. demo/options.py +1 -0
  21. demo/plugins.py +186 -0
  22. demo/seed/__init__.py +0 -0
  23. demo/seed/common.py +127 -0
  24. demo/seed/contributors.py +361 -0
  25. demo/seed/measurements.py +205 -0
  26. demo/seed/projects.py +565 -0
  27. demo/seed/record_search.py +318 -0
  28. demo/seed/samples.py +237 -0
  29. demo/static/scss/_bootstrap_vars.scss +1 -0
  30. demo/static/scss/stylesheet.scss +2 -0
  31. demo/tables.py +59 -0
  32. demo/templates/demo/rocksample_overview.html +32 -0
  33. demo/templates/demo/xrfmeasurement_overview.html +46 -0
  34. demo/templates/fairdm/landing.html +20 -0
  35. demo/tests/__init__.py +0 -0
  36. demo/tests/test_admin_views.py +465 -0
  37. demo/tests/test_api_smoke.py +54 -0
  38. demo/tests/test_contributors.py +136 -0
  39. demo/tests/test_forms.py +70 -0
  40. demo/tests/test_registry_api.py +219 -0
  41. fairdm/__init__.py +13 -0
  42. fairdm/api/__init__.py +1 -0
  43. fairdm/api/apps.py +22 -0
  44. fairdm/api/checks.py +70 -0
  45. fairdm/api/filters.py +503 -0
  46. fairdm/api/pagination.py +40 -0
  47. fairdm/api/permissions.py +103 -0
  48. fairdm/api/router.py +63 -0
  49. fairdm/api/schema.py +366 -0
  50. fairdm/api/serializers.py +1248 -0
  51. fairdm/api/settings.py +128 -0
  52. fairdm/api/throttling.py +56 -0
  53. fairdm/api/urls.py +23 -0
  54. fairdm/api/viewsets.py +408 -0
  55. fairdm/apps.py +129 -0
  56. fairdm/backups/__init__.py +1 -0
  57. fairdm/backups/apps.py +21 -0
  58. fairdm/backups/keys.py +111 -0
  59. fairdm/backups/management/__init__.py +0 -0
  60. fairdm/backups/management/commands/__init__.py +0 -0
  61. fairdm/backups/management/commands/backup.py +40 -0
  62. fairdm/backups/media.py +55 -0
  63. fairdm/backups/run.py +39 -0
  64. fairdm/backups/signals.py +33 -0
  65. fairdm/backups/tasks.py +21 -0
  66. fairdm/conf/__init__.py +101 -0
  67. fairdm/conf/addons.py +146 -0
  68. fairdm/conf/celery.py +10 -0
  69. fairdm/conf/checks.py +664 -0
  70. fairdm/conf/development.py +122 -0
  71. fairdm/conf/environment.py +62 -0
  72. fairdm/conf/settings/__init__.py +1 -0
  73. fairdm/conf/settings/addons.py +417 -0
  74. fairdm/conf/settings/api.py +44 -0
  75. fairdm/conf/settings/apps.py +192 -0
  76. fairdm/conf/settings/auth.py +93 -0
  77. fairdm/conf/settings/cache.py +37 -0
  78. fairdm/conf/settings/celery.py +40 -0
  79. fairdm/conf/settings/database.py +56 -0
  80. fairdm/conf/settings/email.py +39 -0
  81. fairdm/conf/settings/logging.py +72 -0
  82. fairdm/conf/settings/security.py +61 -0
  83. fairdm/conf/settings/static_media.py +108 -0
  84. fairdm/conf/setup.py +373 -0
  85. fairdm/conf/urls.py +74 -0
  86. fairdm/config.py +62 -0
  87. fairdm/contrib/__init__.py +9 -0
  88. fairdm/contrib/admin/__init__.py +1 -0
  89. fairdm/contrib/admin/admin.py +60 -0
  90. fairdm/contrib/admin/apps.py +17 -0
  91. fairdm/contrib/admin/sites.py +117 -0
  92. fairdm/contrib/admin/templates/admin/base_site.html +6 -0
  93. fairdm/contrib/admin/templates/admin/fixture_upload_form.html +14 -0
  94. fairdm/contrib/admin/urls.py +13 -0
  95. fairdm/contrib/admin/views.py +86 -0
  96. fairdm/contrib/autocomplete/__init__.py +3 -0
  97. fairdm/contrib/autocomplete/apps.py +12 -0
  98. fairdm/contrib/autocomplete/fields.py +671 -0
  99. fairdm/contrib/autocomplete/filters.py +94 -0
  100. fairdm/contrib/autocomplete/templates/autocomplete/contributor_option.html +18 -0
  101. fairdm/contrib/autocomplete/templates/autocomplete/record_option.html +8 -0
  102. fairdm/contrib/autocomplete/throttling.py +65 -0
  103. fairdm/contrib/autocomplete/urls.py +66 -0
  104. fairdm/contrib/autocomplete/views.py +1048 -0
  105. fairdm/contrib/collections/README.md +101 -0
  106. fairdm/contrib/collections/__init__.py +3 -0
  107. fairdm/contrib/collections/apps.py +62 -0
  108. fairdm/contrib/collections/tables.py +183 -0
  109. fairdm/contrib/collections/templates/collections/listing.html +33 -0
  110. fairdm/contrib/collections/urls.py +9 -0
  111. fairdm/contrib/collections/views.py +167 -0
  112. fairdm/contrib/contributors/__init__.py +1 -0
  113. fairdm/contrib/contributors/access.py +332 -0
  114. fairdm/contrib/contributors/adapters.py +136 -0
  115. fairdm/contrib/contributors/admin.py +790 -0
  116. fairdm/contrib/contributors/apps.py +34 -0
  117. fairdm/contrib/contributors/choices.py +67 -0
  118. fairdm/contrib/contributors/exceptions.py +9 -0
  119. fairdm/contrib/contributors/filters.py +100 -0
  120. fairdm/contrib/contributors/forms/__init__.py +1 -0
  121. fairdm/contrib/contributors/forms/account.py +79 -0
  122. fairdm/contrib/contributors/forms/contribution.py +186 -0
  123. fairdm/contrib/contributors/forms/forms.py +132 -0
  124. fairdm/contrib/contributors/forms/organization.py +151 -0
  125. fairdm/contrib/contributors/forms/person.py +84 -0
  126. fairdm/contrib/contributors/forms/profile.py +430 -0
  127. fairdm/contrib/contributors/forms/widgets.py +9 -0
  128. fairdm/contrib/contributors/managers.py +311 -0
  129. fairdm/contrib/contributors/migrations/0001_initial.py +616 -0
  130. fairdm/contrib/contributors/migrations/0002_delete_member_remove_person_is_member_and_more.py +22 -0
  131. fairdm/contrib/contributors/migrations/0003_alter_organization_country.py +23 -0
  132. fairdm/contrib/contributors/migrations/0004_delete_contributorrole_and_more.py +60 -0
  133. fairdm/contrib/contributors/migrations/0005_alter_contributor_profile.py +16 -0
  134. fairdm/contrib/contributors/migrations/0006_cleanup_duplicate_types.py +48 -0
  135. fairdm/contrib/contributors/migrations/0007_add_unique_type_constraints.py +17 -0
  136. fairdm/contrib/contributors/migrations/0008_migrate_to_location_model.py +67 -0
  137. fairdm/contrib/contributors/migrations/0009_alter_contributor_lang_and_more.py +26 -0
  138. fairdm/contrib/contributors/migrations/0010_alter_contributoridentifier_managers.py +15 -0
  139. fairdm/contrib/contributors/migrations/0011_add_partial_dates_and_privacy.py +60 -0
  140. fairdm/contrib/contributors/migrations/0012_rename_to_affiliation.py +15 -0
  141. fairdm/contrib/contributors/migrations/0013_update_related_names.py +43 -0
  142. fairdm/contrib/contributors/migrations/0014_add_is_claimed_field.py +19 -0
  143. fairdm/contrib/contributors/migrations/0015_set_is_claimed.py +25 -0
  144. fairdm/contrib/contributors/migrations/0016_make_is_claimed_non_nullable.py +18 -0
  145. fairdm/contrib/contributors/migrations/0017_remove_manage_organization_permission.py +81 -0
  146. fairdm/contrib/contributors/migrations/0018_claiming_audit_log.py +43 -0
  147. fairdm/contrib/contributors/migrations/0019_alter_contributoridentifier_type.py +17 -0
  148. fairdm/contrib/contributors/migrations/0020_contributors_audit.py +148 -0
  149. fairdm/contrib/contributors/migrations/0021_alter_affiliation_end_date_help_text.py +17 -0
  150. fairdm/contrib/contributors/migrations/0022_contribution_level.py +25 -0
  151. fairdm/contrib/contributors/migrations/0023_levels_from_stored_permissions.py +178 -0
  152. fairdm/contrib/contributors/migrations/0024_organization_location.py +29 -0
  153. fairdm/contrib/contributors/migrations/__init__.py +0 -0
  154. fairdm/contrib/contributors/models.py +2212 -0
  155. fairdm/contrib/contributors/permissions.py +115 -0
  156. fairdm/contrib/contributors/plugins/__init__.py +6 -0
  157. fairdm/contrib/contributors/plugins/organisation.py +1 -0
  158. fairdm/contrib/contributors/plugins/overview.py +295 -0
  159. fairdm/contrib/contributors/plugins/person.py +46 -0
  160. fairdm/contrib/contributors/plugins/shared.py +1017 -0
  161. fairdm/contrib/contributors/plugins/update.py +77 -0
  162. fairdm/contrib/contributors/profiles.py +119 -0
  163. fairdm/contrib/contributors/receivers.py +38 -0
  164. fairdm/contrib/contributors/resources.py +110 -0
  165. fairdm/contrib/contributors/services/__init__.py +1 -0
  166. fairdm/contrib/contributors/services/claiming.py +196 -0
  167. fairdm/contrib/contributors/services/crediting.py +374 -0
  168. fairdm/contrib/contributors/services/matching.py +44 -0
  169. fairdm/contrib/contributors/services/merge.py +215 -0
  170. fairdm/contrib/contributors/services/registries.py +441 -0
  171. fairdm/contrib/contributors/signals.py +48 -0
  172. fairdm/contrib/contributors/static/contributors/css/contributor-filter.css +32 -0
  173. fairdm/contrib/contributors/static/contributors/js/contributor-filter.js +79 -0
  174. fairdm/contrib/contributors/tasks.py +233 -0
  175. fairdm/contrib/contributors/templates/contributors/admin/change_form.html +49 -0
  176. fairdm/contrib/contributors/templates/contributors/admin/claim_person.html +77 -0
  177. fairdm/contrib/contributors/templates/contributors/admin/merge_person.html +70 -0
  178. fairdm/contrib/contributors/templates/contributors/claim_profile.html +66 -0
  179. fairdm/contrib/contributors/templates/contributors/contributor_card.html +5 -0
  180. fairdm/contrib/contributors/templates/contributors/organization_detail.html +49 -0
  181. fairdm/contrib/contributors/templates/contributors/overview/organization.html +196 -0
  182. fairdm/contrib/contributors/templates/contributors/overview/person.html +178 -0
  183. fairdm/contrib/contributors/templates/contributors/plugins/contribution_add_organization.html +189 -0
  184. fairdm/contrib/contributors/templates/contributors/plugins/contribution_add_person.html +201 -0
  185. fairdm/contrib/contributors/templates/contributors/plugins/contribution_edit.html +88 -0
  186. fairdm/contrib/contributors/templates/contributors/plugins/contribution_list.html +136 -0
  187. fairdm/contrib/contributors/templates/contributors/plugins/contribution_remove.html +66 -0
  188. fairdm/contrib/contributors/templates/contributors/plugins/update.html +20 -0
  189. fairdm/contrib/contributors/templates/contributors/team.html +14 -0
  190. fairdm/contrib/contributors/templates/cotton/card/affiliations/row.html +18 -0
  191. fairdm/contrib/contributors/templates/cotton/card/affiliations.html +31 -0
  192. fairdm/contrib/contributors/templates/cotton/card/hierarchy/node.html +14 -0
  193. fairdm/contrib/contributors/templates/cotton/card/hierarchy.html +52 -0
  194. fairdm/contrib/contributors/templates/cotton/card/links.html +23 -0
  195. fairdm/contrib/contributors/templates/cotton/card/records.html +51 -0
  196. fairdm/contrib/contributors/templates/cotton/card/roles.html +27 -0
  197. fairdm/contrib/contributors/templates/cotton/contribution/access.html +19 -0
  198. fairdm/contrib/contributors/templates/cotton/contribution/affiliation.html +44 -0
  199. fairdm/contrib/contributors/templates/cotton/contribution/controls.html +30 -0
  200. fairdm/contrib/contributors/templates/cotton/contribution/list_item.html +17 -0
  201. fairdm/contrib/contributors/templates/cotton/contribution/organization.html +38 -0
  202. fairdm/contrib/contributors/templates/cotton/contributor/affiliation.html +4 -0
  203. fairdm/contrib/contributors/templates/cotton/contributor/avatar.html +21 -0
  204. fairdm/contrib/contributors/templates/cotton/contributor/byline.html +28 -0
  205. fairdm/contrib/contributors/templates/cotton/contributor/card/contribution.html +21 -0
  206. fairdm/contrib/contributors/templates/cotton/contributor/card/index.html +12 -0
  207. fairdm/contrib/contributors/templates/cotton/contributor/card/organization.html +37 -0
  208. fairdm/contrib/contributors/templates/cotton/contributor/card/person.html +33 -0
  209. fairdm/contrib/contributors/templates/cotton/contributor/card/person_alt.html +24 -0
  210. fairdm/contrib/contributors/templates/cotton/contributor/icon.html +10 -0
  211. fairdm/contrib/contributors/templates/cotton/contributor/item.html +33 -0
  212. fairdm/contrib/contributors/templates/cotton/contributor/name.html +17 -0
  213. fairdm/contrib/contributors/templates/cotton/contributor/names.html +20 -0
  214. fairdm/contrib/contributors/templates/cotton/contributor/person/affiliation.html +9 -0
  215. fairdm/contrib/contributors/templates/cotton/contributor/person/affiliations.html +11 -0
  216. fairdm/contrib/contributors/templates/cotton/contributor/person/identifier.html +7 -0
  217. fairdm/contrib/contributors/templates/cotton/contributor/row.html +12 -0
  218. fairdm/contrib/contributors/templates/cotton/contributor/stack.html +23 -0
  219. fairdm/contrib/contributors/templates/cotton/contributor/summary.html +18 -0
  220. fairdm/contrib/contributors/templates/cotton/contributor_card.html +4 -0
  221. fairdm/contrib/contributors/templates/cotton/org/avatar.html +20 -0
  222. fairdm/contrib/contributors/templates/cotton/organization_card.html +16 -0
  223. fairdm/contrib/contributors/templates/cotton/person_card.html +23 -0
  224. fairdm/contrib/contributors/templates/widgets/orcid.html +77 -0
  225. fairdm/contrib/contributors/templatetags/__init__.py +1 -0
  226. fairdm/contrib/contributors/templatetags/contributor_tags.py +112 -0
  227. fairdm/contrib/contributors/urls.py +31 -0
  228. fairdm/contrib/contributors/utils/__init__.py +90 -0
  229. fairdm/contrib/contributors/utils/audit.py +47 -0
  230. fairdm/contrib/contributors/utils/helpers.py +94 -0
  231. fairdm/contrib/contributors/utils/tokens.py +77 -0
  232. fairdm/contrib/contributors/utils/transforms.py +958 -0
  233. fairdm/contrib/contributors/validators.py +223 -0
  234. fairdm/contrib/contributors/views/__init__.py +10 -0
  235. fairdm/contrib/contributors/views/claiming.py +111 -0
  236. fairdm/contrib/contributors/views/organization.py +34 -0
  237. fairdm/contrib/contributors/views/person.py +50 -0
  238. fairdm/contrib/contributors/views/team.py +73 -0
  239. fairdm/contrib/identity/__init__.py +1 -0
  240. fairdm/contrib/identity/admin.py +101 -0
  241. fairdm/contrib/identity/apps.py +12 -0
  242. fairdm/contrib/identity/forms.py +1 -0
  243. fairdm/contrib/identity/migrations/0001_initial.py +182 -0
  244. fairdm/contrib/identity/migrations/0002_rename_database_to_identity.py +159 -0
  245. fairdm/contrib/identity/migrations/0003_rename_databasetranslation_identitytranslation_and_more.py +27 -0
  246. fairdm/contrib/identity/migrations/0004_alter_authoritytranslation_unique_together_and_more.py +27 -0
  247. fairdm/contrib/identity/migrations/__init__.py +0 -0
  248. fairdm/contrib/identity/models.py +131 -0
  249. fairdm/contrib/identity/static/configuration/admin.css +28 -0
  250. fairdm/contrib/import_export/__init__.py +1 -0
  251. fairdm/contrib/import_export/apps.py +12 -0
  252. fairdm/contrib/import_export/formats.py +10 -0
  253. fairdm/contrib/import_export/forms.py +30 -0
  254. fairdm/contrib/import_export/resources.py +130 -0
  255. fairdm/contrib/import_export/templates/cotton/import_export/export_dropdown.html +21 -0
  256. fairdm/contrib/import_export/templates/cotton/import_export/result/base_errors.html +1 -0
  257. fairdm/contrib/import_export/templates/cotton/import_export/result/error.html +16 -0
  258. fairdm/contrib/import_export/templates/cotton/import_export/result/preview.html +1 -0
  259. fairdm/contrib/import_export/templates/cotton/import_export/result/validation_errors.html +1 -0
  260. fairdm/contrib/import_export/templates/import_export/import.html +122 -0
  261. fairdm/contrib/import_export/urls.py +5 -0
  262. fairdm/contrib/import_export/utils.py +174 -0
  263. fairdm/contrib/import_export/views.py +378 -0
  264. fairdm/contrib/location/__init__.py +1 -0
  265. fairdm/contrib/location/apps.py +12 -0
  266. fairdm/contrib/location/migrations/0001_initial.py +81 -0
  267. fairdm/contrib/location/migrations/0002_delete_point.py +16 -0
  268. fairdm/contrib/location/migrations/__init__.py +0 -0
  269. fairdm/contrib/plugins/__init__.py +73 -0
  270. fairdm/contrib/plugins/access.py +199 -0
  271. fairdm/contrib/plugins/apps.py +13 -0
  272. fairdm/contrib/plugins/base.py +265 -0
  273. fairdm/contrib/plugins/checks.py +220 -0
  274. fairdm/contrib/plugins/menus.py +16 -0
  275. fairdm/contrib/plugins/mixins.py +39 -0
  276. fairdm/contrib/plugins/registration.py +229 -0
  277. fairdm/contrib/plugins/templates/menus/tab.html +4 -0
  278. fairdm/contrib/plugins/templates/menus/tabs.html +10 -0
  279. fairdm/contrib/plugins/templatetags/__init__.py +1 -0
  280. fairdm/contrib/plugins/templatetags/plugin_tags.py +61 -0
  281. fairdm/contrib/plugins/utils.py +103 -0
  282. fairdm/core/__init__.py +1 -0
  283. fairdm/core/abstract.py +476 -0
  284. fairdm/core/admin.py +21 -0
  285. fairdm/core/apps.py +40 -0
  286. fairdm/core/choices.py +14 -0
  287. fairdm/core/dataset/__init__.py +1 -0
  288. fairdm/core/dataset/admin.py +190 -0
  289. fairdm/core/dataset/apps.py +13 -0
  290. fairdm/core/dataset/filters.py +90 -0
  291. fairdm/core/dataset/forms.py +164 -0
  292. fairdm/core/dataset/migrations/0001_initial.py +216 -0
  293. fairdm/core/dataset/migrations/0002_initial.py +92 -0
  294. fairdm/core/dataset/migrations/0003_alter_dataset_options.py +28 -0
  295. fairdm/core/dataset/migrations/0004_alter_dataset_reference.py +28 -0
  296. fairdm/core/dataset/migrations/0005_alter_dataset_license.py +28 -0
  297. fairdm/core/dataset/migrations/0006_cleanup_duplicate_types.py +61 -0
  298. fairdm/core/dataset/migrations/0007_add_unique_type_constraints.py +31 -0
  299. fairdm/core/dataset/migrations/0008_datasetliteraturerelation_alter_datasetdate_managers_and_more.py +193 -0
  300. fairdm/core/dataset/migrations/0009_alter_dataset_project_on_delete.py +32 -0
  301. fairdm/core/dataset/migrations/0010_add_dataset_permissions.py +30 -0
  302. fairdm/core/dataset/migrations/0011_alter_dataset_options_alter_dataset_license_and_more.py +52 -0
  303. fairdm/core/dataset/migrations/0012_dataset_published_alter_dataset_name.py +23 -0
  304. fairdm/core/dataset/migrations/0013_alter_dataset_options.py +17 -0
  305. fairdm/core/dataset/migrations/0014_alter_datasetdescription_value.py +19 -0
  306. fairdm/core/dataset/migrations/0015_index_modified.py +25 -0
  307. fairdm/core/dataset/migrations/__init__.py +0 -0
  308. fairdm/core/dataset/models.py +532 -0
  309. fairdm/core/dataset/plugins.py +707 -0
  310. fairdm/core/dataset/templates/cotton/dataset/card.html +145 -0
  311. fairdm/core/dataset/templates/dataset/dataset_card.html +2 -0
  312. fairdm/core/dataset/templates/dataset/dataset_detail.html +166 -0
  313. fairdm/core/dataset/templates/publishing/datacite/contributor.xml +14 -0
  314. fairdm/core/dataset/templates/publishing/datacite44.xml +106 -0
  315. fairdm/core/dataset/urls.py +18 -0
  316. fairdm/core/dataset/views.py +62 -0
  317. fairdm/core/dates.py +31 -0
  318. fairdm/core/descriptions.py +85 -0
  319. fairdm/core/editing.py +632 -0
  320. fairdm/core/filters.py +16 -0
  321. fairdm/core/forms.py +160 -0
  322. fairdm/core/formsets.py +68 -0
  323. fairdm/core/image_utils.py +32 -0
  324. fairdm/core/managers.py +162 -0
  325. fairdm/core/measurement/__init__.py +1 -0
  326. fairdm/core/measurement/admin.py +178 -0
  327. fairdm/core/measurement/apps.py +48 -0
  328. fairdm/core/measurement/config.py +94 -0
  329. fairdm/core/measurement/filters.py +130 -0
  330. fairdm/core/measurement/forms.py +145 -0
  331. fairdm/core/measurement/managers.py +160 -0
  332. fairdm/core/measurement/migrations/0001_initial.py +217 -0
  333. fairdm/core/measurement/migrations/0002_initial.py +69 -0
  334. fairdm/core/measurement/migrations/0003_alter_measurement_options.py +27 -0
  335. fairdm/core/measurement/migrations/0004_cleanup_duplicate_types.py +61 -0
  336. fairdm/core/measurement/migrations/0005_add_unique_type_constraints.py +31 -0
  337. fairdm/core/measurement/migrations/0006_alter_measurementdate_managers_and_more.py +37 -0
  338. fairdm/core/measurement/migrations/0007_alter_measurementdate_type_and_more.py +24 -0
  339. fairdm/core/measurement/migrations/0008_measurement_local_id.py +24 -0
  340. fairdm/core/measurement/migrations/0009_alter_measurementidentifier_type.py +19 -0
  341. fairdm/core/measurement/migrations/0010_alter_measurement_local_id.py +18 -0
  342. fairdm/core/measurement/migrations/0011_alter_measurement_sample.py +21 -0
  343. fairdm/core/measurement/migrations/0012_alter_measurement_name.py +18 -0
  344. fairdm/core/measurement/migrations/0013_alter_measurementdescription_value.py +19 -0
  345. fairdm/core/measurement/migrations/0014_index_modified.py +23 -0
  346. fairdm/core/measurement/migrations/0015_located_measurements.py +49 -0
  347. fairdm/core/measurement/migrations/__init__.py +0 -0
  348. fairdm/core/measurement/models.py +339 -0
  349. fairdm/core/measurement/plugins.py +293 -0
  350. fairdm/core/measurement/templates/measurement/measurement_overview.html +167 -0
  351. fairdm/core/measurement/urls.py +13 -0
  352. fairdm/core/models.py +13 -0
  353. fairdm/core/overview.py +134 -0
  354. fairdm/core/permissions.py +76 -0
  355. fairdm/core/plugins.py +573 -0
  356. fairdm/core/project/__init__.py +1 -0
  357. fairdm/core/project/admin.py +203 -0
  358. fairdm/core/project/apps.py +13 -0
  359. fairdm/core/project/filters.py +125 -0
  360. fairdm/core/project/forms.py +116 -0
  361. fairdm/core/project/migrations/0001_initial.py +277 -0
  362. fairdm/core/project/migrations/0002_alter_project_options.py +27 -0
  363. fairdm/core/project/migrations/0003_alter_projectdescription_type.py +19 -0
  364. fairdm/core/project/migrations/0004_cleanup_duplicate_types.py +63 -0
  365. fairdm/core/project/migrations/0005_add_unique_type_constraints.py +31 -0
  366. fairdm/core/project/migrations/0006_alter_project_options_alter_projectdate_options_and_more.py +82 -0
  367. fairdm/core/project/migrations/0007_project_created_by_alter_project_funding_and_more.py +39 -0
  368. fairdm/core/project/migrations/0008_convert_funding_to_datacite_shape.py +45 -0
  369. fairdm/core/project/migrations/0009_alter_project_name.py +18 -0
  370. fairdm/core/project/migrations/0010_alter_projectdescription_value.py +19 -0
  371. fairdm/core/project/migrations/0011_index_modified.py +23 -0
  372. fairdm/core/project/migrations/__init__.py +0 -0
  373. fairdm/core/project/models.py +393 -0
  374. fairdm/core/project/plugins.py +557 -0
  375. fairdm/core/project/receivers.py +5 -0
  376. fairdm/core/project/serializers.py +25 -0
  377. fairdm/core/project/templates/cotton/project/card.html +119 -0
  378. fairdm/core/project/templates/cotton/project/create_button.html +8 -0
  379. fairdm/core/project/templates/project/project_card.html +2 -0
  380. fairdm/core/project/templates/project/project_detail.html +193 -0
  381. fairdm/core/project/transforms.py +219 -0
  382. fairdm/core/project/urls.py +18 -0
  383. fairdm/core/project/validators.py +94 -0
  384. fairdm/core/project/views.py +61 -0
  385. fairdm/core/related_records.py +108 -0
  386. fairdm/core/sample/__init__.py +1 -0
  387. fairdm/core/sample/admin.py +211 -0
  388. fairdm/core/sample/apps.py +13 -0
  389. fairdm/core/sample/config.py +43 -0
  390. fairdm/core/sample/filters.py +138 -0
  391. fairdm/core/sample/forms.py +170 -0
  392. fairdm/core/sample/managers.py +187 -0
  393. fairdm/core/sample/migrations/0001_initial.py +325 -0
  394. fairdm/core/sample/migrations/0002_alter_sample_options.py +27 -0
  395. fairdm/core/sample/migrations/0003_cleanup_duplicate_types.py +61 -0
  396. fairdm/core/sample/migrations/0004_add_unique_type_constraints.py +31 -0
  397. fairdm/core/sample/migrations/0005_alter_sample_options_alter_samplerelation_options_and_more.py +73 -0
  398. fairdm/core/sample/migrations/0006_alter_sample_local_id.py +24 -0
  399. fairdm/core/sample/migrations/0007_alter_sample_status_alter_sample_uuid_and_more.py +31 -0
  400. fairdm/core/sample/migrations/0008_migrate_sample_status_to_unknown.py +23 -0
  401. fairdm/core/sample/migrations/0009_alter_sample_name.py +18 -0
  402. fairdm/core/sample/migrations/0010_alter_sampledescription_value.py +19 -0
  403. fairdm/core/sample/migrations/0011_index_modified.py +23 -0
  404. fairdm/core/sample/migrations/0012_located_samples.py +54 -0
  405. fairdm/core/sample/migrations/__init__.py +0 -0
  406. fairdm/core/sample/models.py +475 -0
  407. fairdm/core/sample/plugins.py +339 -0
  408. fairdm/core/sample/templates/cotton/sample/card.html +74 -0
  409. fairdm/core/sample/templates/sample/sample_detail.html +33 -0
  410. fairdm/core/sample/templates/sample/sample_overview.html +155 -0
  411. fairdm/core/sample/templates/sample/sample_type_detail.html +7 -0
  412. fairdm/core/sample/urls.py +13 -0
  413. fairdm/core/urls.py +10 -0
  414. fairdm/core/utils.py +232 -0
  415. fairdm/core/vocabularies.py +77 -0
  416. fairdm/db/__init__.py +1 -0
  417. fairdm/db/fields.py +61 -0
  418. fairdm/db/managers.py +1 -0
  419. fairdm/db/models.py +148 -0
  420. fairdm/factories/__init__.py +137 -0
  421. fairdm/factories/contributors.py +199 -0
  422. fairdm/factories/core.py +717 -0
  423. fairdm/factories/utils.py +146 -0
  424. fairdm/fixtures/django-waffle.json +35 -0
  425. fairdm/forms/__init__.py +10 -0
  426. fairdm/forms/base.py +227 -0
  427. fairdm/forms/fields.py +195 -0
  428. fairdm/management/__init__.py +1 -0
  429. fairdm/management/commands/__init__.py +1 -0
  430. fairdm/management/commands/cleanup_permissions.py +52 -0
  431. fairdm/management/commands/create_dev_accounts.py +165 -0
  432. fairdm/management/commands/generate_fake_data.py +522 -0
  433. fairdm/management/commands/seed_licenses.py +98 -0
  434. fairdm/management/commands/show_config.py +58 -0
  435. fairdm/management/commands/upgrade_vocabularies.py +385 -0
  436. fairdm/menus.py +137 -0
  437. fairdm/migrations/0001_initial.py +67 -0
  438. fairdm/migrations/0002_rename_taggeditem_table.py +36 -0
  439. fairdm/migrations/__init__.py +0 -0
  440. fairdm/models.py +14 -0
  441. fairdm/permissions.py +55 -0
  442. fairdm/plugins.py +6 -0
  443. fairdm/portal_roles.py +231 -0
  444. fairdm/registry/__init__.py +27 -0
  445. fairdm/registry/config.py +641 -0
  446. fairdm/registry/exceptions.py +176 -0
  447. fairdm/registry/factories.py +1153 -0
  448. fairdm/registry/registry.py +324 -0
  449. fairdm/static/brand/fairdm.svg +1 -0
  450. fairdm/static/brand/icon.png +0 -0
  451. fairdm/static/brand/icon.svg +90 -0
  452. fairdm/static/brand/icon_dark.svg +90 -0
  453. fairdm/static/brand/logo.svg +1 -0
  454. fairdm/static/brand/orcid_16x16.webp +0 -0
  455. fairdm/static/css/fairdm.css +402 -0
  456. fairdm/static/icons/user.svg +7 -0
  457. fairdm/static/img/ORCID.png +0 -0
  458. fairdm/static/img/brand/icon.svg +83 -0
  459. fairdm/static/img/brand/logo.svg +1 -0
  460. fairdm/static/js/chart-theme.js +68 -0
  461. fairdm/templates/api/docs/api_description.md +3 -0
  462. fairdm/templates/api/docs/dataset_list.md +11 -0
  463. fairdm/templates/api/docs/project_list.md +11 -0
  464. fairdm/templates/api/docs/sample_list.md +11 -0
  465. fairdm/templates/base.html +55 -0
  466. fairdm/templates/cards/compact.html +2 -0
  467. fairdm/templates/cards/large.html +46 -0
  468. fairdm/templates/client-side_cropping_widget.html +140 -0
  469. fairdm/templates/cotton/actions/create_new.html +20 -0
  470. fairdm/templates/cotton/actions/manage.html +21 -0
  471. fairdm/templates/cotton/actions/pending.html +21 -0
  472. fairdm/templates/cotton/card/citation.html +29 -0
  473. fairdm/templates/cotton/card/descriptions.html +35 -0
  474. fairdm/templates/cotton/card/details/entry.html +20 -0
  475. fairdm/templates/cotton/card/details.html +55 -0
  476. fairdm/templates/cotton/card/funding.html +41 -0
  477. fairdm/templates/cotton/card/identifiers.html +46 -0
  478. fairdm/templates/cotton/card/location.html +34 -0
  479. fairdm/templates/cotton/card/people.html +44 -0
  480. fairdm/templates/cotton/card/placeholder.html +20 -0
  481. fairdm/templates/cotton/card/readiness.html +49 -0
  482. fairdm/templates/cotton/card/timeline.html +45 -0
  483. fairdm/templates/cotton/components/object_card.html +68 -0
  484. fairdm/templates/cotton/date.html +10 -0
  485. fairdm/templates/cotton/entrance/aside.html +25 -0
  486. fairdm/templates/cotton/icons/orcid.html +4 -0
  487. fairdm/templates/cotton/landing/about.html +45 -0
  488. fairdm/templates/cotton/landing/api.html +29 -0
  489. fairdm/templates/cotton/landing/community.html +42 -0
  490. fairdm/templates/cotton/landing/datasets.html +19 -0
  491. fairdm/templates/cotton/landing/footer.html +33 -0
  492. fairdm/templates/cotton/landing/hero.html +41 -0
  493. fairdm/templates/cotton/landing/join.html +25 -0
  494. fairdm/templates/cotton/landing/projects.html +19 -0
  495. fairdm/templates/cotton/landing/stats.html +11 -0
  496. fairdm/templates/cotton/landing/type_list.html +32 -0
  497. fairdm/templates/cotton/landing/types.html +24 -0
  498. fairdm/templates/cotton/link.html +12 -0
  499. fairdm/templates/cotton/list/index.html +18 -0
  500. fairdm/templates/cotton/list/row.html +14 -0
  501. fairdm/templates/cotton/missing.html +8 -0
  502. fairdm/templates/cotton/nav/link.html +13 -0
  503. fairdm/templates/cotton/progress.html +21 -0
  504. fairdm/templates/cotton/stats/index.html +22 -0
  505. fairdm/templates/cotton/stats/item.html +34 -0
  506. fairdm/templates/cotton/tabs/index.html +22 -0
  507. fairdm/templates/cotton/tabs/tab.html +20 -0
  508. fairdm/templates/django_tomselect/render/option.html +57 -0
  509. fairdm/templates/django_tomselect/tomselect.html +63 -0
  510. fairdm/templates/editing/delete_record.html +38 -0
  511. fairdm/templates/editing/rows.html +6 -0
  512. fairdm/templates/fairdm/dashboard.html +1 -0
  513. fairdm/templates/fairdm/landing.html +78 -0
  514. fairdm/templates/fairdm/plugin.html +10 -0
  515. fairdm/templates/forms/checkbox.html +85 -0
  516. fairdm/templates/forms/fieldset.html +22 -0
  517. fairdm/templates/forms/widgets/array_select2.html +5 -0
  518. fairdm/templates/forms/widgets/quantity_field.html +31 -0
  519. fairdm/templates/forms/widgets/sortable_contributor.html +82 -0
  520. fairdm/templates/forms/widgets/sortable_contributor2.html +81 -0
  521. fairdm/templates/forms/widgets/xpostgres_array_widget.html +24 -0
  522. fairdm/templates/icons/activity.svg +3 -0
  523. fairdm/templates/icons/dashboard.svg +3 -0
  524. fairdm/templates/icons/facebook.svg +1 -0
  525. fairdm/templates/icons/filetype-xml.svg +3 -0
  526. fairdm/templates/icons/grid.svg +3 -0
  527. fairdm/templates/icons/linkedin.svg +1 -0
  528. fairdm/templates/icons/missing_image - Copy.svg +37 -0
  529. fairdm/templates/icons/missing_image.svg +4 -0
  530. fairdm/templates/icons/orcid/authenticated.svg +4 -0
  531. fairdm/templates/icons/orcid/readme.md +3 -0
  532. fairdm/templates/icons/orcid/unauthenticated.svg +5 -0
  533. fairdm/templates/icons/orcid.svg +16 -0
  534. fairdm/templates/icons/organization.svg +1 -0
  535. fairdm/templates/icons/ror.svg +12 -0
  536. fairdm/templates/icons/spinner.svg +1 -0
  537. fairdm/templates/icons/user.svg +7 -0
  538. fairdm/templates/icons/whatsapp.svg +1 -0
  539. fairdm/templates/icons/x_twitter.svg +1 -0
  540. fairdm/templates/layouts/base.html +12 -0
  541. fairdm/templates/location/widgets/location.html +54 -0
  542. fairdm/templates/mvp_accounts/entrance/base.html +34 -0
  543. fairdm/templates/overview/includes/map_library.html +30 -0
  544. fairdm/templates/overview/includes/type_badge.html +48 -0
  545. fairdm/templates/overview/page.html +196 -0
  546. fairdm/templates/pages/api/tos.html +71 -0
  547. fairdm/templates/pages/spectacular.html +26 -0
  548. fairdm/templates/research_vocabs/base.html +42 -0
  549. fairdm/templatetags/__init__.py +1 -0
  550. fairdm/templatetags/fairdm.py +327 -0
  551. fairdm/utils/__init__.py +90 -0
  552. fairdm/utils/choices.py +232 -0
  553. fairdm/utils/context_processors.py +62 -0
  554. fairdm/utils/inspection.py +535 -0
  555. fairdm/utils/location/__init__.py +1 -0
  556. fairdm/utils/location/coverage.py +78 -0
  557. fairdm/utils/location/db.py +53 -0
  558. fairdm/utils/location/forms.py +200 -0
  559. fairdm/utils/location/managers.py +139 -0
  560. fairdm/utils/location/resources.py +190 -0
  561. fairdm/utils/markdown.py +32 -0
  562. fairdm/utils/models.py +18 -0
  563. fairdm/utils/permissions.py +49 -0
  564. fairdm/utils/utils.py +233 -0
  565. fairdm/views/__init__.py +21 -0
  566. fairdm/views/base.py +322 -0
  567. fairdm/views/generic.py +239 -0
  568. fairdm/views/mixins.py +66 -0
  569. fairdm-0.3.0.dist-info/METADATA +506 -0
  570. fairdm-0.3.0.dist-info/RECORD +572 -0
  571. fairdm-0.3.0.dist-info/WHEEL +4 -0
  572. fairdm-0.3.0.dist-info/licenses/LICENSE +21 -0
demo/README.md ADDED
@@ -0,0 +1,466 @@
1
+ # FairDM Demo Application
2
+
3
+ This is the reference implementation and demo portal for the FairDM framework. It demonstrates best practices for building research data portals using FairDM's plugin system, model configuration, and UI components.
4
+
5
+ ## Purpose
6
+
7
+ The demo app serves multiple purposes:
8
+
9
+ 1. **Reference Implementation**: Executable documentation showing how to use FairDM features
10
+ 2. **Testing Ground**: Provides models and data for framework testing
11
+ 3. **Learning Resource**: Examples of plugin patterns, model configuration, and UI composition
12
+ 4. **Quick Start Template**: Starting point for new portal projects
13
+
14
+ ---
15
+
16
+ ## Plugin System Examples
17
+
18
+ The demo app showcases the FairDM plugin system with working examples. See [`demo/plugins.py`](plugins.py) for complete implementations.
19
+
20
+ ### Basic Plugin Registration
21
+
22
+ ```python
23
+ from fairdm import plugins
24
+ from fairdm.contrib.plugins import Plugin
25
+ from fairdm.core.plugins import BaseOverviewPlugin, BaseEditPlugin
26
+
27
+
28
+ @plugins.register(Sample)
29
+ class SampleOverview(BaseOverviewPlugin):
30
+ """Basic overview inheriting standard behavior."""
31
+
32
+ def get_context_data(self, **kwargs):
33
+ context = super().get_context_data(**kwargs)
34
+ context["measurements"] = self.object.measurements.all()
35
+ return context
36
+ ```
37
+
38
+ ### Form-Based Edit Plugin
39
+
40
+ ```python
41
+ @plugins.register(Sample)
42
+ class SampleEdit(BaseEditPlugin):
43
+ """Edit form with custom permissions."""
44
+
45
+ form_class = SampleForm
46
+ menu = {"label": "Edit", "icon": "pencil", "order": 10}
47
+ permission = "demo.change_sample"
48
+ ```
49
+
50
+ ### Polymorphic Visibility (Conditional Plugins)
51
+
52
+ ```python
53
+ @plugins.register(Sample)
54
+ class LocationDetailsPlugin(Plugin, TemplateView):
55
+ """Only appears for samples with locations."""
56
+
57
+ menu = {"label": "Location Details", "icon": "geo-alt", "order": 50}
58
+
59
+ @staticmethod
60
+ def check(request, obj):
61
+ """Custom visibility check."""
62
+ return obj and hasattr(obj, "location") and obj.location is not None
63
+ ```
64
+
65
+ ### Plugin with Static Assets
66
+
67
+ The demo includes examples of plugins with CSS and JavaScript:
68
+
69
+ ```python
70
+ @plugins.register(Sample)
71
+ class ChartPlugin(Plugin, TemplateView):
72
+ menu = {"label": "Charts", "icon": "chart", "order": 40}
73
+
74
+ class Media:
75
+ css = {"all": ("demo/charts.css",)}
76
+ js = (
77
+ "https://cdn.jsdelivr.net/npm/chart.js",
78
+ "demo/charts.js",
79
+ )
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Model Configuration
85
+
86
+ The demo app demonstrates custom Sample and Measurement types:
87
+
88
+ ### Custom Sample Model
89
+
90
+ See [`demo/models/sample.py`](models/sample.py):
91
+
92
+ ```python
93
+ class DemoSample(Sample):
94
+ """Example Sample subclass with custom fields."""
95
+
96
+ char_field = models.CharField(max_length=100)
97
+ text_field = models.TextField()
98
+ date_field = models.DateField()
99
+ # ... more field examples
100
+ ```
101
+
102
+ ### Custom Measurement Model
103
+
104
+ See [`demo/models/measurement.py`](models/measurement.py):
105
+
106
+ ```python
107
+ class DemoMeasurement(Measurement):
108
+ """Example Measurement subclass."""
109
+
110
+ value = models.FloatField()
111
+ unit = models.CharField(max_length=50)
112
+ # ... more field examples
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Registry Configuration
118
+
119
+ The demo shows how to configure model display using the registry system.
120
+
121
+ See [`demo/options.py`](options.py):
122
+
123
+ ```python
124
+ from fairdm.core.registry import registry
125
+
126
+
127
+ @registry.register(DemoSample)
128
+ class DemoSampleOptions:
129
+ list_fields = ["name", "char_field", "date_field"]
130
+ detail_fields = ["name", "char_field", "text_field", "date_field"]
131
+ filter_fields = ["char_field", "date_field"]
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Running the Demo
137
+
138
+ ### 1. Create Migrations
139
+
140
+ ```bash
141
+ uv run python manage.py makemigrations demo
142
+ ```
143
+
144
+ ### 2. Apply Migrations
145
+
146
+ ```bash
147
+ uv run python manage.py migrate
148
+ ```
149
+
150
+ ### 3. Create Sample Data
151
+
152
+ ```bash
153
+ uv run python manage.py shell
154
+
155
+ from demo.factories import DemoSampleFactory, DemoMeasurementFactory
156
+
157
+ # Create 10 demo samples
158
+ samples = DemoSampleFactory.create_batch(10)
159
+
160
+ # Create 5 measurements per sample
161
+ for sample in samples:
162
+ DemoMeasurementFactory.create_batch(5, sample=sample)
163
+ ```
164
+
165
+ ### 4. Run Development Server
166
+
167
+ ```bash
168
+ uv run python manage.py runserver
169
+ ```
170
+
171
+ ### 5. Navigate to Demo Content
172
+
173
+ - **Projects**: http://localhost:8000/projects/
174
+ - **Datasets**: http://localhost:8000/datasets/
175
+ - **Samples**: http://localhost:8000/samples/
176
+ - **Measurements**: http://localhost:8000/measurements/
177
+
178
+ ---
179
+
180
+ ## Project Structure
181
+
182
+ ```
183
+ demo/
184
+ ├── README.md ← This file
185
+ ├── apps.py ← App configuration
186
+ ├── models.py ← Polymorphic Sample/Measurement models
187
+ ├── plugins.py ← Example plugin implementations
188
+ ├── options.py ← Registry configuration
189
+ ├── factories.py ← Test data factories
190
+ ├── forms.py ← Form definitions
191
+ ├── tables.py ← django-tables2 table definitions
192
+ ├── filters.py ← django-filter filterset definitions
193
+ ├── tests/ ← Test suite
194
+ ├── templates/ ← Demo-specific templates
195
+ │ └── demo/
196
+ │ └── plugins/ ← Plugin templates
197
+ └── static/ ← Demo-specific static assets
198
+ └── demo/
199
+ ├── css/
200
+ └── js/
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Key Learning Examples
206
+
207
+ ### 1. Plugin Inheritance Patterns
208
+
209
+ The demo shows three inheritance approaches:
210
+
211
+ - **Framework Base Classes**: Inherit from `BaseOverviewPlugin`, `BaseEditPlugin`, `BaseDeletePlugin`
212
+ - **Custom Base Classes**: Create reusable bases in your package
213
+ - **Direct Implementation**: Combine `Plugin` + Django CBV directly
214
+
215
+ See [`plugins.py`](plugins.py) for side-by-side examples.
216
+
217
+ ### 2. Reusable Plugin Components
218
+
219
+ The demo demonstrates creating base plugins that portal developers can inherit:
220
+
221
+ ```python
222
+ # Base class (reusable)
223
+ class GeologyAnalysisPlugin(Plugin, TemplateView):
224
+ menu = {"label": "Geology", "order": 30}
225
+ template_name = "geology/analysis.html"
226
+
227
+ def analyze_geology(self, sample):
228
+ """Override in subclasses."""
229
+ return {}
230
+
231
+
232
+ # Portal-specific customization (inherits from base)
233
+ @plugins.register(Sample)
234
+ class CustomGeologyPlugin(GeologyAnalysisPlugin):
235
+ def analyze_geology(self, sample):
236
+ return {"custom_metric": sample.calculate_metric()}
237
+ ```
238
+
239
+ ### 3. Form-Based Plugins
240
+
241
+ Examples of plugins using Django's UpdateView, DeleteView, FormView:
242
+
243
+ - **Edit plugins**: Update object fields with crispy forms
244
+ - **Delete plugins**: Confirmation and cascade behavior
245
+ - **Wizard plugins**: Multi-step form workflows
246
+
247
+ ### 4. List View Plugins
248
+
249
+ Show related objects in plugin tabs:
250
+
251
+ ```python
252
+ @plugins.register(Project)
253
+ class ProjectDatasets(Plugin, DatasetListView):
254
+ menu = {"label": "Datasets", "icon": "dataset", "order": 20}
255
+
256
+ def get_queryset(self):
257
+ return self.object.datasets.all()
258
+ ```
259
+
260
+ ### 5. Permission-Based Plugins
261
+
262
+ Examples of:
263
+ - Model-level permissions (`permission = "app.change_model"`)
264
+ - Object-level permissions (django-guardian integration)
265
+ - Custom permission logic (`has_permission()` override)
266
+
267
+ ---
268
+
269
+ ## Testing
270
+
271
+ Run demo app tests:
272
+
273
+ ```bash
274
+ # All tests
275
+ uv run pytest demo/tests/
276
+
277
+ # Specific test module
278
+ uv run pytest demo/tests/test_plugins.py
279
+
280
+ # With coverage
281
+ uv run pytest demo/tests/ --cov=demo
282
+ ```
283
+
284
+ ---
285
+
286
+ ## Using Demo Code in Your Portal
287
+
288
+ The demo app code is designed to be copied and adapted:
289
+
290
+ 1. **Copy plugin patterns** from `plugins.py` to your app
291
+ 2. **Adapt model examples** from `models.py` to your domain
292
+ 3. **Reuse factory patterns** from `factories.py` for test data
293
+ 4. **Reference templates** in `templates/demo/plugins/`
294
+
295
+ ### Example: Adapting a Plugin
296
+
297
+ From demo:
298
+ ```python
299
+ @plugins.register(Sample)
300
+ class LocationDetailsPlugin(Plugin, TemplateView):
301
+ check = lambda r, o: o and hasattr(o, "location") and o.location
302
+ menu = {"label": "Location", "order": 50}
303
+ ```
304
+
305
+ To your portal:
306
+ ```python
307
+ @plugins.register(MySample)
308
+ class MyLocationPlugin(Plugin, TemplateView):
309
+ check = lambda r, o: o and o.has_geolocation()
310
+ menu = {"label": "Geolocation", "order": 50}
311
+ template_name = "myplugins/location.html"
312
+
313
+ def get_context_data(self, **kwargs):
314
+ context = super().get_context_data(**kwargs)
315
+ context["map_data"] = self.object.get_map_coordinates()
316
+ return context
317
+ ```
318
+
319
+ ---
320
+
321
+ ## Contributor Features (Feature 009)
322
+
323
+ The demo app demonstrates contributor-related features including Person/Organization models, affiliation workflows, metadata transforms, and privacy controls.
324
+
325
+ ### Person Model (AUTH_USER_MODEL)
326
+
327
+ FairDM uses `Person` as the `AUTH_USER_MODEL`. There are two patterns:
328
+
329
+ **Claimed Users** (interactive portal access):
330
+ ```python
331
+ from fairdm.contrib.contributors.models import Person
332
+
333
+ # Create a claimed user
334
+ person = Person.objects.create_user(
335
+ email="researcher@example.com",
336
+ first_name="Jane",
337
+ last_name="Researcher",
338
+ password="secure_password",
339
+ )
340
+ assert person.is_claimed # True: has email, password, and is_active
341
+ ```
342
+
343
+ **Unclaimed Contributors** (provenance-only records):
344
+ ```python
345
+ # Create unclaimed contributor (no login capability)
346
+ unclaimed = Person.objects.create_unclaimed(
347
+ first_name="Historical",
348
+ last_name="Contributor",
349
+ )
350
+ assert not unclaimed.is_claimed # False: no email, no password, inactive
351
+ ```
352
+
353
+ ### Organization Ownership
354
+
355
+ Organizations use guardian permissions for ownership. The `Affiliation` type field determines permissions:
356
+
357
+ ```python
358
+ from fairdm.contrib.contributors.models import Organization, Affiliation
359
+
360
+ org = Organization.objects.create(name="Research Lab")
361
+ person = Person.objects.get(email="researcher@example.com")
362
+
363
+ # Create OWNER-type affiliation (grants manage_organization permission automatically)
364
+ affiliation = Affiliation.objects.create(
365
+ person=person,
366
+ organization=org,
367
+ type=Affiliation.MembershipType.OWNER, # 3
368
+ )
369
+
370
+ # Permission granted via lifecycle hook
371
+ assert person.has_perm("contributors.manage_organization", org) # True
372
+ ```
373
+
374
+ ### Metadata Transforms
375
+
376
+ Export contributors to standard metadata formats:
377
+
378
+ ```python
379
+ from fairdm.contrib.contributors.utils.transforms import (
380
+ DataCiteTransform,
381
+ SchemaOrgTransform,
382
+ CSLJSONTransform,
383
+ ORCIDTransform,
384
+ )
385
+
386
+ # Export Person to DataCite JSON
387
+ datacite_json = DataCiteTransform().export(person)
388
+ # Returns: {
389
+ # "givenName": "Jane",
390
+ # "familyName": "Researcher",
391
+ # "affiliation": [...]
392
+ # }
393
+
394
+ # Export Organization to Schema.org JSON-LD
395
+ schema_org_json = SchemaOrgTransform().export(org)
396
+ # Returns: {
397
+ # "@type": "Organization",
398
+ # "@id": "https://ror.org/...",
399
+ # "name": "Research Lab"
400
+ # }
401
+
402
+ # Export Person to CSL-JSON (for citations)
403
+ csl_json = CSLJSONTransform().export(person)
404
+ # Returns: {
405
+ # "type": "person",
406
+ # "given": "Jane",
407
+ # "family": "Researcher"
408
+ # }
409
+ ```
410
+
411
+ Import from external identifiers:
412
+
413
+ ```python
414
+ # Import from ORCID (async task)
415
+ person = ORCIDTransform().import_data(orcid_api_response)
416
+
417
+ # Import from ROR (async task)
418
+ org = Organization.from_ror("https://ror.org/04aj4c181")
419
+ ```
420
+
421
+ ### Privacy Controls
422
+
423
+ Control which fields are visible based on privacy settings:
424
+
425
+ ```python
426
+ # Get visible fields for anonymous viewer
427
+ visible = person.get_visible_fields(viewer=None)
428
+ # Returns: {"name": "Jane Researcher", "affiliation": "..."}
429
+ # Email hidden by default for claimed users
430
+
431
+ # Privacy settings (JSONField)
432
+ person.privacy_settings = {
433
+ "email": "private", # Hide from everyone except self
434
+ "affiliation": "public", # Show to everyone
435
+ "orcid": "authenticated", # Show only to logged-in users
436
+ }
437
+ person.save()
438
+
439
+ # Check visibility for authenticated viewer
440
+ visible = person.get_visible_fields(viewer=some_authenticated_user)
441
+ # Returns: {"name": "...", "affiliation": "...", "orcid": "..."}
442
+ ```
443
+
444
+ See [docs/developers/reference/contributor-models.md](../docs/developers/reference/contributor-models.md) for complete API reference.
445
+
446
+ ---
447
+
448
+ ## Contributing to Demo
449
+
450
+ The demo app is living documentation. When adding framework features:
451
+
452
+ 1. **Add working examples** demonstrating the feature
453
+ 2. **Include docstrings** explaining the purpose and pattern
454
+ 3. **Link to documentation** with references to relevant guides
455
+ 4. **Write tests** validating the example works correctly
456
+
457
+ See [Contributing Guide](../docs/contributing/index.md) for details.
458
+
459
+ ---
460
+
461
+ ## Further Reading
462
+
463
+ - [Plugin System Documentation](../docs/developers/how-to/create-a-plugin.md)
464
+ - [Framework Architecture](../specs/008-plugin-system/plan.md)
465
+ - [Quick Start Examples](../specs/008-plugin-system/quickstart.md)
466
+ - [API Reference](../docs/api/plugins.md)
demo/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Reference application showing how to build a portal on FairDM."""