wbcore 1.59.9__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.
Files changed (1239) hide show
  1. wbcore/__init__.py +1 -0
  2. wbcore/admin.py +197 -0
  3. wbcore/apps.py +17 -0
  4. wbcore/cache/__init__.py +0 -0
  5. wbcore/cache/buttons.py +23 -0
  6. wbcore/cache/decorators.py +31 -0
  7. wbcore/cache/mixins.py +49 -0
  8. wbcore/cache/registry.py +98 -0
  9. wbcore/cache/views.py +19 -0
  10. wbcore/configs/__init__.py +11 -0
  11. wbcore/configs/configs.py +52 -0
  12. wbcore/configs/decorators.py +11 -0
  13. wbcore/configs/registry.py +35 -0
  14. wbcore/configs/views.py +12 -0
  15. wbcore/configurations/__init__.py +1 -0
  16. wbcore/configurations/base.py +46 -0
  17. wbcore/configurations/configurations/__init__.py +16 -0
  18. wbcore/configurations/configurations/apps.py +55 -0
  19. wbcore/configurations/configurations/authentication.py +44 -0
  20. wbcore/configurations/configurations/base.py +15 -0
  21. wbcore/configurations/configurations/cache.py +20 -0
  22. wbcore/configurations/configurations/celery.py +26 -0
  23. wbcore/configurations/configurations/i18nl10n.py +11 -0
  24. wbcore/configurations/configurations/mail.py +2 -0
  25. wbcore/configurations/configurations/maintenance.py +53 -0
  26. wbcore/configurations/configurations/media.py +22 -0
  27. wbcore/configurations/configurations/middleware.py +27 -0
  28. wbcore/configurations/configurations/network.py +19 -0
  29. wbcore/configurations/configurations/rest_framework.py +42 -0
  30. wbcore/configurations/configurations/static.py +28 -0
  31. wbcore/configurations/configurations/templates.py +17 -0
  32. wbcore/configurations/configurations/uvicorn.py +9 -0
  33. wbcore/configurations/configurations/wbcore.py +61 -0
  34. wbcore/content_type/__init__.py +0 -0
  35. wbcore/content_type/admin.py +8 -0
  36. wbcore/content_type/filters.py +20 -0
  37. wbcore/content_type/serializers.py +98 -0
  38. wbcore/content_type/utils.py +30 -0
  39. wbcore/content_type/viewsets.py +81 -0
  40. wbcore/contrib/__init__.py +0 -0
  41. wbcore/contrib/agenda/__init__.py +0 -0
  42. wbcore/contrib/agenda/admin/__init__.py +2 -0
  43. wbcore/contrib/agenda/admin/calendar_item.py +14 -0
  44. wbcore/contrib/agenda/admin/conference_room.py +19 -0
  45. wbcore/contrib/agenda/apps.py +6 -0
  46. wbcore/contrib/agenda/configurations.py +11 -0
  47. wbcore/contrib/agenda/factories/__init__.py +2 -0
  48. wbcore/contrib/agenda/factories/calendar_item.py +49 -0
  49. wbcore/contrib/agenda/factories/conference_room.py +20 -0
  50. wbcore/contrib/agenda/filters/__init__.py +2 -0
  51. wbcore/contrib/agenda/filters/calendar_item.py +66 -0
  52. wbcore/contrib/agenda/filters/conference_room.py +42 -0
  53. wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po +206 -0
  54. wbcore/contrib/agenda/locale/de/LC_MESSAGES/django.po.translated +236 -0
  55. wbcore/contrib/agenda/locale/en/LC_MESSAGES/django.po +200 -0
  56. wbcore/contrib/agenda/locale/fr/LC_MESSAGES/django.po +201 -0
  57. wbcore/contrib/agenda/migrations/0001_initial.py +84 -0
  58. wbcore/contrib/agenda/migrations/0002_initial.py +26 -0
  59. wbcore/contrib/agenda/migrations/0003_calendaritem_endpoint_basename.py +42 -0
  60. wbcore/contrib/agenda/migrations/0004_alter_calendaritem_item_type.py +17 -0
  61. wbcore/contrib/agenda/migrations/0005_building_and_more.py +94 -0
  62. wbcore/contrib/agenda/migrations/0006_calendaritem_is_deletable.py +17 -0
  63. wbcore/contrib/agenda/migrations/0007_alter_calendaritem_options.py +21 -0
  64. wbcore/contrib/agenda/migrations/0008_alter_calendaritem_item_type.py +17 -0
  65. wbcore/contrib/agenda/migrations/0009_alter_calendaritem_icon.py +18 -0
  66. wbcore/contrib/agenda/migrations/__init__.py +0 -0
  67. wbcore/contrib/agenda/models/__init__.py +2 -0
  68. wbcore/contrib/agenda/models/calendar_item.py +240 -0
  69. wbcore/contrib/agenda/models/conference_room.py +96 -0
  70. wbcore/contrib/agenda/release_notes/1_0_0.md +13 -0
  71. wbcore/contrib/agenda/release_notes/__init__.py +0 -0
  72. wbcore/contrib/agenda/serializers/__init__.py +10 -0
  73. wbcore/contrib/agenda/serializers/calendar_item.py +77 -0
  74. wbcore/contrib/agenda/serializers/conference_room.py +99 -0
  75. wbcore/contrib/agenda/signals.py +5 -0
  76. wbcore/contrib/agenda/static/agenda/markdown/documentation/building.md +11 -0
  77. wbcore/contrib/agenda/static/agenda/markdown/documentation/conferenceroom.md +20 -0
  78. wbcore/contrib/agenda/tests/__init__.py +0 -0
  79. wbcore/contrib/agenda/tests/conftest.py +14 -0
  80. wbcore/contrib/agenda/tests/signals.py +17 -0
  81. wbcore/contrib/agenda/tests/test_models.py +34 -0
  82. wbcore/contrib/agenda/tests/test_viewsets.py +172 -0
  83. wbcore/contrib/agenda/tests/tests.py +26 -0
  84. wbcore/contrib/agenda/typings.py +19 -0
  85. wbcore/contrib/agenda/urls.py +26 -0
  86. wbcore/contrib/agenda/viewsets/__init__.py +13 -0
  87. wbcore/contrib/agenda/viewsets/buttons/__init__.py +1 -0
  88. wbcore/contrib/agenda/viewsets/buttons/conference_room.py +21 -0
  89. wbcore/contrib/agenda/viewsets/calendar_items.py +169 -0
  90. wbcore/contrib/agenda/viewsets/conference_room.py +48 -0
  91. wbcore/contrib/agenda/viewsets/display/__init__.py +2 -0
  92. wbcore/contrib/agenda/viewsets/display/calendar_items.py +17 -0
  93. wbcore/contrib/agenda/viewsets/display/conference_room.py +42 -0
  94. wbcore/contrib/agenda/viewsets/endpoints/__init__.py +1 -0
  95. wbcore/contrib/agenda/viewsets/endpoints/calendar_items.py +19 -0
  96. wbcore/contrib/agenda/viewsets/menu/__init__.py +2 -0
  97. wbcore/contrib/agenda/viewsets/menu/calendar_items.py +12 -0
  98. wbcore/contrib/agenda/viewsets/menu/conference_room.py +38 -0
  99. wbcore/contrib/agenda/viewsets/titles/__init__.py +2 -0
  100. wbcore/contrib/agenda/viewsets/titles/calendar_items.py +8 -0
  101. wbcore/contrib/agenda/viewsets/titles/conference_room.py +25 -0
  102. wbcore/contrib/ai/__init__.py +0 -0
  103. wbcore/contrib/ai/apps.py +5 -0
  104. wbcore/contrib/ai/exceptions.py +40 -0
  105. wbcore/contrib/ai/llm/__init__.py +0 -0
  106. wbcore/contrib/ai/llm/config.py +166 -0
  107. wbcore/contrib/ai/llm/decorators.py +10 -0
  108. wbcore/contrib/ai/llm/mixins.py +34 -0
  109. wbcore/contrib/ai/llm/utils.py +69 -0
  110. wbcore/contrib/authentication/__init__.py +9 -0
  111. wbcore/contrib/authentication/admin.py +247 -0
  112. wbcore/contrib/authentication/apps.py +14 -0
  113. wbcore/contrib/authentication/authentication.py +111 -0
  114. wbcore/contrib/authentication/configs.py +10 -0
  115. wbcore/contrib/authentication/configurations.py +60 -0
  116. wbcore/contrib/authentication/dynamic_preferences_registry.py +26 -0
  117. wbcore/contrib/authentication/factories/__init__.py +10 -0
  118. wbcore/contrib/authentication/factories/tokens.py +15 -0
  119. wbcore/contrib/authentication/factories/users.py +112 -0
  120. wbcore/contrib/authentication/factories/users_activities.py +19 -0
  121. wbcore/contrib/authentication/filters.py +17 -0
  122. wbcore/contrib/authentication/fixtures/authentication.json +62 -0
  123. wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po +632 -0
  124. wbcore/contrib/authentication/locale/de/LC_MESSAGES/django.po.translated +634 -0
  125. wbcore/contrib/authentication/locale/en/LC_MESSAGES/django.po +590 -0
  126. wbcore/contrib/authentication/locale/fr/LC_MESSAGES/django.po +592 -0
  127. wbcore/contrib/authentication/management/__init__.py +14 -0
  128. wbcore/contrib/authentication/migrations/0001_initial_squashed.py +174 -0
  129. wbcore/contrib/authentication/migrations/0002_profile.py +26 -0
  130. wbcore/contrib/authentication/migrations/0003_alter_user_profile.py +34 -0
  131. wbcore/contrib/authentication/migrations/0004_token.py +51 -0
  132. wbcore/contrib/authentication/migrations/0005_user_external_calendar_settings.py +17 -0
  133. wbcore/contrib/authentication/migrations/0006_auto_20231206_1422.py +13 -0
  134. wbcore/contrib/authentication/migrations/__init__.py +0 -0
  135. wbcore/contrib/authentication/models/__init__.py +3 -0
  136. wbcore/contrib/authentication/models/tokens.py +140 -0
  137. wbcore/contrib/authentication/models/users.py +224 -0
  138. wbcore/contrib/authentication/models/users_activities.py +114 -0
  139. wbcore/contrib/authentication/release_notes/1_0_0.md +13 -0
  140. wbcore/contrib/authentication/release_notes/__init__.py +0 -0
  141. wbcore/contrib/authentication/serializers/__init__.py +14 -0
  142. wbcore/contrib/authentication/serializers/users.py +351 -0
  143. wbcore/contrib/authentication/serializers/users_activites.py +37 -0
  144. wbcore/contrib/authentication/tasks.py +30 -0
  145. wbcore/contrib/authentication/templates/activate_confirm.html +12 -0
  146. wbcore/contrib/authentication/templates/base.html +135 -0
  147. wbcore/contrib/authentication/templates/email_base_template.html +335 -0
  148. wbcore/contrib/authentication/templates/password_reset_done.html +13 -0
  149. wbcore/contrib/authentication/templates/password_reset_email.html +11 -0
  150. wbcore/contrib/authentication/templates/password_reset_email_html.html +43 -0
  151. wbcore/contrib/authentication/templates/password_reset_form.html +23 -0
  152. wbcore/contrib/authentication/templates/password_reset_sent.html +11 -0
  153. wbcore/contrib/authentication/templates/reset_password.html +15 -0
  154. wbcore/contrib/authentication/templates/user_registration_email.html +37 -0
  155. wbcore/contrib/authentication/tests/__init__.py +0 -0
  156. wbcore/contrib/authentication/tests/conftest.py +18 -0
  157. wbcore/contrib/authentication/tests/e2e/__init__.py +1 -0
  158. wbcore/contrib/authentication/tests/e2e/e2e_auth_utility.py +20 -0
  159. wbcore/contrib/authentication/tests/signals.py +18 -0
  160. wbcore/contrib/authentication/tests/test_configs.py +6 -0
  161. wbcore/contrib/authentication/tests/test_serializers.py +6 -0
  162. wbcore/contrib/authentication/tests/test_tasks.py +34 -0
  163. wbcore/contrib/authentication/tests/test_tokens.py +127 -0
  164. wbcore/contrib/authentication/tests/test_users.py +338 -0
  165. wbcore/contrib/authentication/tests/test_viewsets.py +6 -0
  166. wbcore/contrib/authentication/tests/tests.py +15 -0
  167. wbcore/contrib/authentication/urls.py +94 -0
  168. wbcore/contrib/authentication/utils.py +10 -0
  169. wbcore/contrib/authentication/viewsets/__init__.py +19 -0
  170. wbcore/contrib/authentication/viewsets/buttons/__init__.py +1 -0
  171. wbcore/contrib/authentication/viewsets/buttons/users.py +56 -0
  172. wbcore/contrib/authentication/viewsets/display/__init__.py +6 -0
  173. wbcore/contrib/authentication/viewsets/display/user_activities.py +86 -0
  174. wbcore/contrib/authentication/viewsets/display/users.py +73 -0
  175. wbcore/contrib/authentication/viewsets/endpoints/__init__.py +6 -0
  176. wbcore/contrib/authentication/viewsets/endpoints/user_activities.py +14 -0
  177. wbcore/contrib/authentication/viewsets/endpoints/users.py +18 -0
  178. wbcore/contrib/authentication/viewsets/menu/__init__.py +6 -0
  179. wbcore/contrib/authentication/viewsets/menu/user_activities.py +40 -0
  180. wbcore/contrib/authentication/viewsets/menu/users.py +17 -0
  181. wbcore/contrib/authentication/viewsets/titles/__init__.py +12 -0
  182. wbcore/contrib/authentication/viewsets/titles/user_activities.py +32 -0
  183. wbcore/contrib/authentication/viewsets/titles/users.py +27 -0
  184. wbcore/contrib/authentication/viewsets/user_activities.py +224 -0
  185. wbcore/contrib/authentication/viewsets/users.py +331 -0
  186. wbcore/contrib/color/admin.py +28 -0
  187. wbcore/contrib/color/apps.py +5 -0
  188. wbcore/contrib/color/enums.py +17 -0
  189. wbcore/contrib/color/factories.py +10 -0
  190. wbcore/contrib/color/fields.py +29 -0
  191. wbcore/contrib/color/forms.py +13 -0
  192. wbcore/contrib/color/migrations/0001_initial.py +33 -0
  193. wbcore/contrib/color/migrations/0002_alter_colorgradient_colors.py +25 -0
  194. wbcore/contrib/color/migrations/__init__.py +0 -0
  195. wbcore/contrib/color/models.py +63 -0
  196. wbcore/contrib/color/tests/conftest.py +10 -0
  197. wbcore/contrib/color/tests/test_color_models.py +61 -0
  198. wbcore/contrib/color/tests/test_fields.py +25 -0
  199. wbcore/contrib/currency/__init__.py +0 -0
  200. wbcore/contrib/currency/admin.py +33 -0
  201. wbcore/contrib/currency/apps.py +5 -0
  202. wbcore/contrib/currency/dynamic_preferences_registry.py +39 -0
  203. wbcore/contrib/currency/factories.py +40 -0
  204. wbcore/contrib/currency/fixtures/currency.yaml +1014 -0
  205. wbcore/contrib/currency/fixtures/currency_fx_rate.yaml +73585 -0
  206. wbcore/contrib/currency/import_export/__init__.py +0 -0
  207. wbcore/contrib/currency/import_export/backends/__init__.py +1 -0
  208. wbcore/contrib/currency/import_export/backends/fixerio/__init__.py +1 -0
  209. wbcore/contrib/currency/import_export/backends/fixerio/currency_fx_rates.py +69 -0
  210. wbcore/contrib/currency/import_export/backends/utils.py +5 -0
  211. wbcore/contrib/currency/import_export/handlers/__init__.py +2 -0
  212. wbcore/contrib/currency/import_export/handlers/currency.py +25 -0
  213. wbcore/contrib/currency/import_export/handlers/currency_fx_rates.py +29 -0
  214. wbcore/contrib/currency/import_export/parsers/__init__.py +0 -0
  215. wbcore/contrib/currency/import_export/parsers/fixerio/__init__.py +0 -0
  216. wbcore/contrib/currency/import_export/parsers/fixerio/currency_fx_rates.py +34 -0
  217. wbcore/contrib/currency/migrations/0001_initial.py +89 -0
  218. wbcore/contrib/currency/migrations/__init__.py +0 -0
  219. wbcore/contrib/currency/models.py +199 -0
  220. wbcore/contrib/currency/release_notes/1_0_0.md +13 -0
  221. wbcore/contrib/currency/release_notes/__init__.py +0 -0
  222. wbcore/contrib/currency/serializers.py +45 -0
  223. wbcore/contrib/currency/tests/__init__.py +0 -0
  224. wbcore/contrib/currency/tests/conftest.py +7 -0
  225. wbcore/contrib/currency/tests/test_models.py +115 -0
  226. wbcore/contrib/currency/tests/test_serializers.py +67 -0
  227. wbcore/contrib/currency/tests/test_viewsets.py +196 -0
  228. wbcore/contrib/currency/urls.py +20 -0
  229. wbcore/contrib/currency/viewsets/__init__.py +2 -0
  230. wbcore/contrib/currency/viewsets/buttons/__init__.py +0 -0
  231. wbcore/contrib/currency/viewsets/currency.py +57 -0
  232. wbcore/contrib/currency/viewsets/currency_fx_rates.py +33 -0
  233. wbcore/contrib/currency/viewsets/display/__init__.py +2 -0
  234. wbcore/contrib/currency/viewsets/display/currency.py +38 -0
  235. wbcore/contrib/currency/viewsets/display/currency_fx_rates.py +9 -0
  236. wbcore/contrib/currency/viewsets/endpoints/__init__.py +1 -0
  237. wbcore/contrib/currency/viewsets/endpoints/currency_fx_rates.py +6 -0
  238. wbcore/contrib/currency/viewsets/menu/__init__.py +1 -0
  239. wbcore/contrib/currency/viewsets/menu/currency.py +8 -0
  240. wbcore/contrib/currency/viewsets/preview/__init__.py +1 -0
  241. wbcore/contrib/currency/viewsets/preview/currency.py +10 -0
  242. wbcore/contrib/currency/viewsets/titles/__init__.py +2 -0
  243. wbcore/contrib/currency/viewsets/titles/currency.py +6 -0
  244. wbcore/contrib/currency/viewsets/titles/currency_fx_rates.py +8 -0
  245. wbcore/contrib/dataloader/__init__.py +1 -0
  246. wbcore/contrib/dataloader/apps.py +5 -0
  247. wbcore/contrib/dataloader/dataloaders/__init__.py +2 -0
  248. wbcore/contrib/dataloader/dataloaders/dataloaders.py +25 -0
  249. wbcore/contrib/dataloader/dataloaders/proxies.py +82 -0
  250. wbcore/contrib/dataloader/models/__init__.py +1 -0
  251. wbcore/contrib/dataloader/models/entities.py +30 -0
  252. wbcore/contrib/dataloader/models/querysets/__init__.py +1 -0
  253. wbcore/contrib/dataloader/models/querysets/entities.py +28 -0
  254. wbcore/contrib/dataloader/tests/__init__.py +0 -0
  255. wbcore/contrib/dataloader/tests/conftest.py +26 -0
  256. wbcore/contrib/dataloader/tests/test/__init__.py +0 -0
  257. wbcore/contrib/dataloader/tests/test/dataloaders/__init__.py +0 -0
  258. wbcore/contrib/dataloader/tests/test/dataloaders/dataloaders.py +18 -0
  259. wbcore/contrib/dataloader/tests/test/dataloaders/protocols.py +5 -0
  260. wbcore/contrib/dataloader/tests/test/dataloaders/proxies.py +11 -0
  261. wbcore/contrib/dataloader/tests/test/factories.py +13 -0
  262. wbcore/contrib/dataloader/tests/test/models.py +11 -0
  263. wbcore/contrib/dataloader/tests/test_dataloaders.py +38 -0
  264. wbcore/contrib/dataloader/tests/test_entities.py +13 -0
  265. wbcore/contrib/dataloader/utils.py +20 -0
  266. wbcore/contrib/directory/__init__.py +0 -0
  267. wbcore/contrib/directory/admin/__init__.py +14 -0
  268. wbcore/contrib/directory/admin/contacts.py +55 -0
  269. wbcore/contrib/directory/admin/entries.py +235 -0
  270. wbcore/contrib/directory/admin/relationships.py +73 -0
  271. wbcore/contrib/directory/apps.py +6 -0
  272. wbcore/contrib/directory/configs.py +9 -0
  273. wbcore/contrib/directory/configurations.py +70 -0
  274. wbcore/contrib/directory/dynamic_preferences_registry.py +61 -0
  275. wbcore/contrib/directory/factories/__init__.py +35 -0
  276. wbcore/contrib/directory/factories/contacts.py +71 -0
  277. wbcore/contrib/directory/factories/entries.py +174 -0
  278. wbcore/contrib/directory/factories/relationships.py +77 -0
  279. wbcore/contrib/directory/filters/__init__.py +22 -0
  280. wbcore/contrib/directory/filters/contacts.py +234 -0
  281. wbcore/contrib/directory/filters/entries.py +273 -0
  282. wbcore/contrib/directory/filters/relationships.py +90 -0
  283. wbcore/contrib/directory/fixtures/directory.json +3884 -0
  284. wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po +1700 -0
  285. wbcore/contrib/directory/locale/de/LC_MESSAGES/django.po.translated +1779 -0
  286. wbcore/contrib/directory/locale/en/LC_MESSAGES/django.po +1652 -0
  287. wbcore/contrib/directory/locale/fr/LC_MESSAGES/django.po +1654 -0
  288. wbcore/contrib/directory/migrations/0001_initial.py +871 -0
  289. wbcore/contrib/directory/migrations/0002_auto_20230414_1553.py +42 -0
  290. wbcore/contrib/directory/migrations/0003_remove_entry_last_event.py +16 -0
  291. wbcore/contrib/directory/migrations/0004_entry_is_draft_entry.py +21 -0
  292. wbcore/contrib/directory/migrations/0005_entry_salutation.py +17 -0
  293. wbcore/contrib/directory/migrations/0006_employeremployeerelationship_position_name.py +23 -0
  294. wbcore/contrib/directory/migrations/0007_alter_bankingcontact_options.py +20 -0
  295. wbcore/contrib/directory/migrations/0008_bankingcontact_access.py +17 -0
  296. wbcore/contrib/directory/migrations/0009_remove_entry_external_identfier_and_more.py +22 -0
  297. wbcore/contrib/directory/migrations/0010_remove_addresscontact_city.py +31 -0
  298. wbcore/contrib/directory/migrations/0011_person_description_person_i18n.py +24 -0
  299. wbcore/contrib/directory/migrations/0012_alter_person_managers.py +20 -0
  300. wbcore/contrib/directory/migrations/0013_alter_clientmanagerrelationship_options.py +17 -0
  301. wbcore/contrib/directory/migrations/0014_alter_entry_relationship_managers_and_more.py +28 -0
  302. wbcore/contrib/directory/migrations/__init__.py +0 -0
  303. wbcore/contrib/directory/models/__init__.py +27 -0
  304. wbcore/contrib/directory/models/contacts.py +554 -0
  305. wbcore/contrib/directory/models/entries.py +896 -0
  306. wbcore/contrib/directory/models/relationships.py +624 -0
  307. wbcore/contrib/directory/permissions.py +6 -0
  308. wbcore/contrib/directory/preferences.py +11 -0
  309. wbcore/contrib/directory/release_notes/1_0_0.md +13 -0
  310. wbcore/contrib/directory/release_notes/1_0_1.md +13 -0
  311. wbcore/contrib/directory/release_notes/__init__.py +0 -0
  312. wbcore/contrib/directory/serializers/__init__.py +62 -0
  313. wbcore/contrib/directory/serializers/companies.py +169 -0
  314. wbcore/contrib/directory/serializers/contacts.py +404 -0
  315. wbcore/contrib/directory/serializers/entries.py +355 -0
  316. wbcore/contrib/directory/serializers/entry_representations.py +38 -0
  317. wbcore/contrib/directory/serializers/persons.py +214 -0
  318. wbcore/contrib/directory/serializers/relationships.py +333 -0
  319. wbcore/contrib/directory/signals.py +3 -0
  320. wbcore/contrib/directory/static/directory/markdown/documentation/address.md +38 -0
  321. wbcore/contrib/directory/static/directory/markdown/documentation/banking.md +54 -0
  322. wbcore/contrib/directory/static/directory/markdown/documentation/bankingentry.md +38 -0
  323. wbcore/contrib/directory/static/directory/markdown/documentation/clientmanagerrelationship.md +42 -0
  324. wbcore/contrib/directory/static/directory/markdown/documentation/company.md +52 -0
  325. wbcore/contrib/directory/static/directory/markdown/documentation/companytype.md +2 -0
  326. wbcore/contrib/directory/static/directory/markdown/documentation/customerstatus.md +2 -0
  327. wbcore/contrib/directory/static/directory/markdown/documentation/email.md +20 -0
  328. wbcore/contrib/directory/static/directory/markdown/documentation/employeecompany.md +34 -0
  329. wbcore/contrib/directory/static/directory/markdown/documentation/employerperson.md +43 -0
  330. wbcore/contrib/directory/static/directory/markdown/documentation/person.md +61 -0
  331. wbcore/contrib/directory/static/directory/markdown/documentation/position.md +2 -0
  332. wbcore/contrib/directory/static/directory/markdown/documentation/relationshiptype.md +2 -0
  333. wbcore/contrib/directory/static/directory/markdown/documentation/socialmedia.md +23 -0
  334. wbcore/contrib/directory/static/directory/markdown/documentation/specialization.md +2 -0
  335. wbcore/contrib/directory/static/directory/markdown/documentation/systememployee.md +31 -0
  336. wbcore/contrib/directory/static/directory/markdown/documentation/telephone.md +23 -0
  337. wbcore/contrib/directory/static/directory/markdown/documentation/telephonesearch.md +26 -0
  338. wbcore/contrib/directory/static/directory/markdown/documentation/userisclient.md +14 -0
  339. wbcore/contrib/directory/static/directory/markdown/documentation/userismanager.md +28 -0
  340. wbcore/contrib/directory/static/directory/markdown/documentation/website.md +20 -0
  341. wbcore/contrib/directory/tests/__init__.py +0 -0
  342. wbcore/contrib/directory/tests/conftest.py +62 -0
  343. wbcore/contrib/directory/tests/disable_signals.py +62 -0
  344. wbcore/contrib/directory/tests/e2e/__init__.py +7 -0
  345. wbcore/contrib/directory/tests/e2e/e2e_directory_utility.py +163 -0
  346. wbcore/contrib/directory/tests/signals.py +89 -0
  347. wbcore/contrib/directory/tests/test_configs.py +6 -0
  348. wbcore/contrib/directory/tests/test_filters.py +60 -0
  349. wbcore/contrib/directory/tests/test_models.py +428 -0
  350. wbcore/contrib/directory/tests/test_permissions.py +123 -0
  351. wbcore/contrib/directory/tests/test_serializers.py +217 -0
  352. wbcore/contrib/directory/tests/test_viewsets.py +835 -0
  353. wbcore/contrib/directory/typings.py +17 -0
  354. wbcore/contrib/directory/urls.py +135 -0
  355. wbcore/contrib/directory/viewsets/__init__.py +57 -0
  356. wbcore/contrib/directory/viewsets/buttons/__init__.py +7 -0
  357. wbcore/contrib/directory/viewsets/buttons/contacts.py +17 -0
  358. wbcore/contrib/directory/viewsets/buttons/entries.py +134 -0
  359. wbcore/contrib/directory/viewsets/buttons/relationships.py +64 -0
  360. wbcore/contrib/directory/viewsets/contacts.py +392 -0
  361. wbcore/contrib/directory/viewsets/display/__init__.py +36 -0
  362. wbcore/contrib/directory/viewsets/display/contacts.py +368 -0
  363. wbcore/contrib/directory/viewsets/display/entries.py +532 -0
  364. wbcore/contrib/directory/viewsets/display/relationships.py +292 -0
  365. wbcore/contrib/directory/viewsets/display/utils.py +34 -0
  366. wbcore/contrib/directory/viewsets/endpoints/__init__.py +22 -0
  367. wbcore/contrib/directory/viewsets/endpoints/contacts.py +55 -0
  368. wbcore/contrib/directory/viewsets/endpoints/entries.py +31 -0
  369. wbcore/contrib/directory/viewsets/endpoints/relationships.py +81 -0
  370. wbcore/contrib/directory/viewsets/entries.py +205 -0
  371. wbcore/contrib/directory/viewsets/menu/__init__.py +16 -0
  372. wbcore/contrib/directory/viewsets/menu/contacts.py +26 -0
  373. wbcore/contrib/directory/viewsets/menu/entries.py +62 -0
  374. wbcore/contrib/directory/viewsets/menu/relationships.py +32 -0
  375. wbcore/contrib/directory/viewsets/menu/utils.py +73 -0
  376. wbcore/contrib/directory/viewsets/mixins.py +12 -0
  377. wbcore/contrib/directory/viewsets/previews/__init__.py +2 -0
  378. wbcore/contrib/directory/viewsets/previews/contacts.py +19 -0
  379. wbcore/contrib/directory/viewsets/previews/entries.py +32 -0
  380. wbcore/contrib/directory/viewsets/relationships.py +256 -0
  381. wbcore/contrib/directory/viewsets/titles/__init__.py +30 -0
  382. wbcore/contrib/directory/viewsets/titles/contacts.py +123 -0
  383. wbcore/contrib/directory/viewsets/titles/entries.py +30 -0
  384. wbcore/contrib/directory/viewsets/titles/relationships.py +86 -0
  385. wbcore/contrib/directory/viewsets/titles/utils.py +47 -0
  386. wbcore/contrib/directory/viewsets/utils.py +101 -0
  387. wbcore/contrib/documents/__init__.py +0 -0
  388. wbcore/contrib/documents/admin.py +80 -0
  389. wbcore/contrib/documents/apps.py +6 -0
  390. wbcore/contrib/documents/factories.py +44 -0
  391. wbcore/contrib/documents/filters.py +81 -0
  392. wbcore/contrib/documents/fixtures/docments.json +135 -0
  393. wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po +281 -0
  394. wbcore/contrib/documents/locale/de/LC_MESSAGES/django.po.translated +285 -0
  395. wbcore/contrib/documents/locale/en/LC_MESSAGES/django.po +271 -0
  396. wbcore/contrib/documents/locale/fr/LC_MESSAGES/django.po +270 -0
  397. wbcore/contrib/documents/migrations/0001_initial.py +189 -0
  398. wbcore/contrib/documents/migrations/0002_documentmodelrelationship_primary_and_more.py +22 -0
  399. wbcore/contrib/documents/migrations/0003_alter_documentmodelrelationship_unique_together_and_more.py +30 -0
  400. wbcore/contrib/documents/migrations/0004_auto_20240103_0958.py +44 -0
  401. wbcore/contrib/documents/migrations/0005_document_valid_from_document_valid_until_and_more.py +32 -0
  402. wbcore/contrib/documents/migrations/__init__.py +0 -0
  403. wbcore/contrib/documents/models/__init__.py +4 -0
  404. wbcore/contrib/documents/models/document_model_relationships.py +58 -0
  405. wbcore/contrib/documents/models/document_types.py +36 -0
  406. wbcore/contrib/documents/models/documents.py +311 -0
  407. wbcore/contrib/documents/models/mixins.py +10 -0
  408. wbcore/contrib/documents/models/shareable_links.py +91 -0
  409. wbcore/contrib/documents/release_notes/1_0_0.md +13 -0
  410. wbcore/contrib/documents/release_notes/__init__.py +0 -0
  411. wbcore/contrib/documents/serializers/__init__.py +12 -0
  412. wbcore/contrib/documents/serializers/document_model_relationships.py +31 -0
  413. wbcore/contrib/documents/serializers/document_types.py +41 -0
  414. wbcore/contrib/documents/serializers/documents.py +65 -0
  415. wbcore/contrib/documents/serializers/shareable_links.py +91 -0
  416. wbcore/contrib/documents/static/documents/markdown/documentation/document_types.md +21 -0
  417. wbcore/contrib/documents/static/documents/markdown/documentation/documents.md +18 -0
  418. wbcore/contrib/documents/static/documents/markdown/documentation/shareablelink.md +28 -0
  419. wbcore/contrib/documents/static/documents/markdown/documentation/shareablelinkaccess.md +20 -0
  420. wbcore/contrib/documents/tests/conftest.py +30 -0
  421. wbcore/contrib/documents/tests/test_models.py +148 -0
  422. wbcore/contrib/documents/urls.py +43 -0
  423. wbcore/contrib/documents/viewsets/__init__.py +10 -0
  424. wbcore/contrib/documents/viewsets/buttons/__init__.py +3 -0
  425. wbcore/contrib/documents/viewsets/buttons/documents.py +54 -0
  426. wbcore/contrib/documents/viewsets/buttons/shareable_links.py +21 -0
  427. wbcore/contrib/documents/viewsets/buttons/signals.py +14 -0
  428. wbcore/contrib/documents/viewsets/display/__init__.py +4 -0
  429. wbcore/contrib/documents/viewsets/display/document_model_relationships.py +19 -0
  430. wbcore/contrib/documents/viewsets/display/document_types.py +24 -0
  431. wbcore/contrib/documents/viewsets/display/documents.py +102 -0
  432. wbcore/contrib/documents/viewsets/display/shareable_links.py +66 -0
  433. wbcore/contrib/documents/viewsets/document_model_relationships.py +25 -0
  434. wbcore/contrib/documents/viewsets/document_types.py +38 -0
  435. wbcore/contrib/documents/viewsets/documents.py +108 -0
  436. wbcore/contrib/documents/viewsets/endpoints/__init__.py +5 -0
  437. wbcore/contrib/documents/viewsets/endpoints/documents.py +20 -0
  438. wbcore/contrib/documents/viewsets/endpoints/documents_model_relationships.py +14 -0
  439. wbcore/contrib/documents/viewsets/endpoints/shareable_links.py +27 -0
  440. wbcore/contrib/documents/viewsets/menu/__init__.py +1 -0
  441. wbcore/contrib/documents/viewsets/menu/documents.py +25 -0
  442. wbcore/contrib/documents/viewsets/previews/__init__.py +1 -0
  443. wbcore/contrib/documents/viewsets/previews/documents.py +10 -0
  444. wbcore/contrib/documents/viewsets/shareable_links.py +110 -0
  445. wbcore/contrib/documents/viewsets/titles/__init__.py +3 -0
  446. wbcore/contrib/documents/viewsets/titles/document_types.py +14 -0
  447. wbcore/contrib/documents/viewsets/titles/documents.py +27 -0
  448. wbcore/contrib/documents/viewsets/titles/shareable_links.py +14 -0
  449. wbcore/contrib/dynamic_preferences/__init__.py +0 -0
  450. wbcore/contrib/dynamic_preferences/types.py +127 -0
  451. wbcore/contrib/dynamic_preferences/viewsets.py +27 -0
  452. wbcore/contrib/example_app/__init__.py +0 -0
  453. wbcore/contrib/example_app/admin.py +118 -0
  454. wbcore/contrib/example_app/apps.py +6 -0
  455. wbcore/contrib/example_app/factories/__init__.py +8 -0
  456. wbcore/contrib/example_app/factories/event.py +27 -0
  457. wbcore/contrib/example_app/factories/league.py +16 -0
  458. wbcore/contrib/example_app/factories/match.py +26 -0
  459. wbcore/contrib/example_app/factories/person.py +34 -0
  460. wbcore/contrib/example_app/factories/role.py +10 -0
  461. wbcore/contrib/example_app/factories/sport.py +13 -0
  462. wbcore/contrib/example_app/factories/stadium.py +13 -0
  463. wbcore/contrib/example_app/factories/team.py +37 -0
  464. wbcore/contrib/example_app/filters/__init__.py +9 -0
  465. wbcore/contrib/example_app/filters/event.py +83 -0
  466. wbcore/contrib/example_app/filters/league.py +75 -0
  467. wbcore/contrib/example_app/filters/match.py +93 -0
  468. wbcore/contrib/example_app/filters/person.py +78 -0
  469. wbcore/contrib/example_app/filters/role.py +18 -0
  470. wbcore/contrib/example_app/filters/sport.py +27 -0
  471. wbcore/contrib/example_app/filters/stadium.py +31 -0
  472. wbcore/contrib/example_app/filters/team.py +78 -0
  473. wbcore/contrib/example_app/filters/teamresult.py +48 -0
  474. wbcore/contrib/example_app/fixtures/example_app.json +7425 -0
  475. wbcore/contrib/example_app/migrations/0001_initial.py +498 -0
  476. wbcore/contrib/example_app/migrations/0002_sportperson_profile.py +49 -0
  477. wbcore/contrib/example_app/migrations/0003_change_stadium_capacity.py +31 -0
  478. wbcore/contrib/example_app/migrations/0004_alter_player_transfer_value.py +21 -0
  479. wbcore/contrib/example_app/migrations/0005_sportperson_profile_image.py +23 -0
  480. wbcore/contrib/example_app/migrations/0006_league_season_period_player_is_active_and_more.py +116 -0
  481. wbcore/contrib/example_app/migrations/0007_alter_player_options_alter_team_options_and_more.py +40 -0
  482. wbcore/contrib/example_app/migrations/__init__.py +0 -0
  483. wbcore/contrib/example_app/models.py +978 -0
  484. wbcore/contrib/example_app/serializers/__init__.py +31 -0
  485. wbcore/contrib/example_app/serializers/league.py +112 -0
  486. wbcore/contrib/example_app/serializers/match_event.py +305 -0
  487. wbcore/contrib/example_app/serializers/person_team.py +281 -0
  488. wbcore/contrib/example_app/serializers/role.py +16 -0
  489. wbcore/contrib/example_app/serializers/season.py +54 -0
  490. wbcore/contrib/example_app/serializers/sport.py +38 -0
  491. wbcore/contrib/example_app/serializers/stadium.py +59 -0
  492. wbcore/contrib/example_app/serializers/teamresult.py +44 -0
  493. wbcore/contrib/example_app/templates/example_app/embedded_view.html +19 -0
  494. wbcore/contrib/example_app/tests/__init__.py +0 -0
  495. wbcore/contrib/example_app/tests/conftest.py +13 -0
  496. wbcore/contrib/example_app/tests/e2e/__init__.py +1 -0
  497. wbcore/contrib/example_app/tests/e2e/e2e_example_app_utility.py +52 -0
  498. wbcore/contrib/example_app/tests/e2e/test_league.py +70 -0
  499. wbcore/contrib/example_app/tests/e2e/test_person.py +68 -0
  500. wbcore/contrib/example_app/tests/e2e/test_teams.py +57 -0
  501. wbcore/contrib/example_app/tests/signals.py +7 -0
  502. wbcore/contrib/example_app/tests/test_displays.py +41 -0
  503. wbcore/contrib/example_app/tests/test_filters.py +71 -0
  504. wbcore/contrib/example_app/tests/test_models/test_event.py +89 -0
  505. wbcore/contrib/example_app/tests/test_models/test_match.py +220 -0
  506. wbcore/contrib/example_app/tests/test_models/test_others.py +161 -0
  507. wbcore/contrib/example_app/tests/test_serializers/test_league_serializer.py +34 -0
  508. wbcore/contrib/example_app/tests/test_serializers/test_match_serializer.py +139 -0
  509. wbcore/contrib/example_app/tests/test_serializers/test_role_serializer.py +13 -0
  510. wbcore/contrib/example_app/tests/test_serializers/test_sport_serializer.py +14 -0
  511. wbcore/contrib/example_app/tests/test_serializers/test_stadium_serializer.py +14 -0
  512. wbcore/contrib/example_app/tests/test_serializers/test_team_result_serializer.py +30 -0
  513. wbcore/contrib/example_app/tests/test_serializers/test_team_serializer.py +70 -0
  514. wbcore/contrib/example_app/tests/test_utils.py +26 -0
  515. wbcore/contrib/example_app/tests/test_viewsets/test_event_viewset.py +167 -0
  516. wbcore/contrib/example_app/tests/test_viewsets/test_league_viewset.py +84 -0
  517. wbcore/contrib/example_app/tests/test_viewsets/test_match_viewset.py +65 -0
  518. wbcore/contrib/example_app/tests/test_viewsets/test_person_viewset.py +166 -0
  519. wbcore/contrib/example_app/tests/test_viewsets/test_role_viewset.py +75 -0
  520. wbcore/contrib/example_app/tests/test_viewsets/test_sport_viewset.py +75 -0
  521. wbcore/contrib/example_app/tests/test_viewsets/test_stadium_viewset.py +75 -0
  522. wbcore/contrib/example_app/tests/test_viewsets/test_team_viewset.py +92 -0
  523. wbcore/contrib/example_app/tests/test_viewsets/test_teamresult_viewset.py +58 -0
  524. wbcore/contrib/example_app/tests/test_viewsets/test_utils_viewsets.py +124 -0
  525. wbcore/contrib/example_app/urls.py +82 -0
  526. wbcore/contrib/example_app/utils.py +21 -0
  527. wbcore/contrib/example_app/views.py +6 -0
  528. wbcore/contrib/example_app/viewsets/__init__.py +29 -0
  529. wbcore/contrib/example_app/viewsets/buttons/__init__.py +2 -0
  530. wbcore/contrib/example_app/viewsets/buttons/person.py +19 -0
  531. wbcore/contrib/example_app/viewsets/buttons/team.py +24 -0
  532. wbcore/contrib/example_app/viewsets/displays/__init__.py +23 -0
  533. wbcore/contrib/example_app/viewsets/displays/event.py +187 -0
  534. wbcore/contrib/example_app/viewsets/displays/league.py +391 -0
  535. wbcore/contrib/example_app/viewsets/displays/match.py +265 -0
  536. wbcore/contrib/example_app/viewsets/displays/person.py +252 -0
  537. wbcore/contrib/example_app/viewsets/displays/role.py +17 -0
  538. wbcore/contrib/example_app/viewsets/displays/season.py +66 -0
  539. wbcore/contrib/example_app/viewsets/displays/sport.py +116 -0
  540. wbcore/contrib/example_app/viewsets/displays/stadium.py +150 -0
  541. wbcore/contrib/example_app/viewsets/displays/team.py +250 -0
  542. wbcore/contrib/example_app/viewsets/displays/teamresult.py +31 -0
  543. wbcore/contrib/example_app/viewsets/endpoints/__init__.py +12 -0
  544. wbcore/contrib/example_app/viewsets/endpoints/endpoints.py +80 -0
  545. wbcore/contrib/example_app/viewsets/event.py +288 -0
  546. wbcore/contrib/example_app/viewsets/league.py +73 -0
  547. wbcore/contrib/example_app/viewsets/match.py +120 -0
  548. wbcore/contrib/example_app/viewsets/menu/__init__.py +1 -0
  549. wbcore/contrib/example_app/viewsets/menu/menus.py +103 -0
  550. wbcore/contrib/example_app/viewsets/menus.py +64 -0
  551. wbcore/contrib/example_app/viewsets/person.py +134 -0
  552. wbcore/contrib/example_app/viewsets/role.py +25 -0
  553. wbcore/contrib/example_app/viewsets/season.py +23 -0
  554. wbcore/contrib/example_app/viewsets/sport.py +26 -0
  555. wbcore/contrib/example_app/viewsets/stadium.py +31 -0
  556. wbcore/contrib/example_app/viewsets/team.py +69 -0
  557. wbcore/contrib/example_app/viewsets/teamresult.py +107 -0
  558. wbcore/contrib/example_app/viewsets/titles/__init__.py +9 -0
  559. wbcore/contrib/example_app/viewsets/titles/event.py +42 -0
  560. wbcore/contrib/example_app/viewsets/titles/league.py +25 -0
  561. wbcore/contrib/example_app/viewsets/titles/match.py +35 -0
  562. wbcore/contrib/example_app/viewsets/titles/person.py +36 -0
  563. wbcore/contrib/example_app/viewsets/titles/role.py +14 -0
  564. wbcore/contrib/example_app/viewsets/titles/sport.py +14 -0
  565. wbcore/contrib/example_app/viewsets/titles/stadium.py +14 -0
  566. wbcore/contrib/example_app/viewsets/titles/team.py +25 -0
  567. wbcore/contrib/example_app/viewsets/titles/teamresult.py +13 -0
  568. wbcore/contrib/geography/__init__.py +0 -0
  569. wbcore/contrib/geography/admin.py +30 -0
  570. wbcore/contrib/geography/apps.py +5 -0
  571. wbcore/contrib/geography/factories.py +46 -0
  572. wbcore/contrib/geography/fixtures/geography.json +13454 -0
  573. wbcore/contrib/geography/import_export/__init__.py +0 -0
  574. wbcore/contrib/geography/import_export/resources/__init__.py +0 -0
  575. wbcore/contrib/geography/import_export/resources/geography.py +11 -0
  576. wbcore/contrib/geography/migrations/0001_initial.py +110 -0
  577. wbcore/contrib/geography/migrations/0002_geography_geography_geography_tree_i739a.py +17 -0
  578. wbcore/contrib/geography/migrations/__init__.py +0 -0
  579. wbcore/contrib/geography/models.py +150 -0
  580. wbcore/contrib/geography/release_notes/1_0_0.md +13 -0
  581. wbcore/contrib/geography/release_notes/__init__.py +0 -0
  582. wbcore/contrib/geography/serializers.py +29 -0
  583. wbcore/contrib/geography/static/geography/markdown/documentation/geography.md +16 -0
  584. wbcore/contrib/geography/tests/__init__.py +0 -0
  585. wbcore/contrib/geography/tests/conftest.py +26 -0
  586. wbcore/contrib/geography/tests/signals.py +7 -0
  587. wbcore/contrib/geography/tests/test_models.py +27 -0
  588. wbcore/contrib/geography/tests/test_viewsets.py +100 -0
  589. wbcore/contrib/geography/urls.py +13 -0
  590. wbcore/contrib/geography/viewsets/__init__.py +1 -0
  591. wbcore/contrib/geography/viewsets/buttons/__init__.py +0 -0
  592. wbcore/contrib/geography/viewsets/display/__init__.py +1 -0
  593. wbcore/contrib/geography/viewsets/display/geography.py +32 -0
  594. wbcore/contrib/geography/viewsets/endpoints/__init__.py +0 -0
  595. wbcore/contrib/geography/viewsets/geography.py +58 -0
  596. wbcore/contrib/geography/viewsets/menu/__init__.py +1 -0
  597. wbcore/contrib/geography/viewsets/menu/geography.py +8 -0
  598. wbcore/contrib/geography/viewsets/preview/__init__.py +1 -0
  599. wbcore/contrib/geography/viewsets/preview/geography.py +19 -0
  600. wbcore/contrib/geography/viewsets/titles/__init__.py +0 -0
  601. wbcore/contrib/geography/viewsets/titles/geography.py +14 -0
  602. wbcore/contrib/gleap/__init__.py +0 -0
  603. wbcore/contrib/gleap/apps.py +5 -0
  604. wbcore/contrib/gleap/configs.py +12 -0
  605. wbcore/contrib/gleap/configurations.py +6 -0
  606. wbcore/contrib/gleap/hashes.py +13 -0
  607. wbcore/contrib/gleap/tests/__init__.py +0 -0
  608. wbcore/contrib/gleap/tests/conftest.py +1 -0
  609. wbcore/contrib/gleap/tests/tests.py +29 -0
  610. wbcore/contrib/gleap/urls.py +8 -0
  611. wbcore/contrib/gleap/views.py +31 -0
  612. wbcore/contrib/guardian/apps.py +6 -0
  613. wbcore/contrib/guardian/configurations.py +3 -0
  614. wbcore/contrib/guardian/filters.py +21 -0
  615. wbcore/contrib/guardian/migrations/0001_initial.py +103 -0
  616. wbcore/contrib/guardian/migrations/__init__.py +0 -0
  617. wbcore/contrib/guardian/models/__init__.py +1 -0
  618. wbcore/contrib/guardian/models/mixins.py +139 -0
  619. wbcore/contrib/guardian/models/models.py +29 -0
  620. wbcore/contrib/guardian/tasks.py +11 -0
  621. wbcore/contrib/guardian/tests/__init__.py +0 -0
  622. wbcore/contrib/guardian/tests/conftest.py +1 -0
  623. wbcore/contrib/guardian/tests/test_model_mixins.py +92 -0
  624. wbcore/contrib/guardian/tests/test_tasks.py +77 -0
  625. wbcore/contrib/guardian/tests/test_utils.py +196 -0
  626. wbcore/contrib/guardian/tests/test_viewsets.py +48 -0
  627. wbcore/contrib/guardian/urls.py +12 -0
  628. wbcore/contrib/guardian/utils.py +124 -0
  629. wbcore/contrib/guardian/viewsets/__init__.py +1 -0
  630. wbcore/contrib/guardian/viewsets/configs/__init__.py +4 -0
  631. wbcore/contrib/guardian/viewsets/configs/buttons.py +36 -0
  632. wbcore/contrib/guardian/viewsets/configs/displays.py +43 -0
  633. wbcore/contrib/guardian/viewsets/configs/endpoints.py +25 -0
  634. wbcore/contrib/guardian/viewsets/configs/titles.py +16 -0
  635. wbcore/contrib/guardian/viewsets/mixins.py +6 -0
  636. wbcore/contrib/guardian/viewsets/viewsets.py +141 -0
  637. wbcore/contrib/i18n/__init__.py +2 -0
  638. wbcore/contrib/i18n/buttons.py +33 -0
  639. wbcore/contrib/i18n/serializers/__init__.py +0 -0
  640. wbcore/contrib/i18n/serializers/fields.py +20 -0
  641. wbcore/contrib/i18n/serializers/mixins.py +13 -0
  642. wbcore/contrib/i18n/tests/conftest.py +11 -0
  643. wbcore/contrib/i18n/tests/test_viewsets.py +67 -0
  644. wbcore/contrib/i18n/translation.py +141 -0
  645. wbcore/contrib/i18n/viewsets.py +36 -0
  646. wbcore/contrib/icons/__init__.py +1 -0
  647. wbcore/contrib/icons/apps.py +5 -0
  648. wbcore/contrib/icons/backends/__init__.py +5 -0
  649. wbcore/contrib/icons/backends/default.py +375 -0
  650. wbcore/contrib/icons/backends/material.py +136 -0
  651. wbcore/contrib/icons/icons.py +167 -0
  652. wbcore/contrib/icons/models.py +11 -0
  653. wbcore/contrib/icons/serializers.py +18 -0
  654. wbcore/contrib/io/__init__.py +0 -0
  655. wbcore/contrib/io/admin.py +151 -0
  656. wbcore/contrib/io/apps.py +37 -0
  657. wbcore/contrib/io/backends/__init__.py +3 -0
  658. wbcore/contrib/io/backends/abstract.py +60 -0
  659. wbcore/contrib/io/backends/mail.py +39 -0
  660. wbcore/contrib/io/backends/utils.py +39 -0
  661. wbcore/contrib/io/configs/__init__.py +0 -0
  662. wbcore/contrib/io/configs/endpoints.py +13 -0
  663. wbcore/contrib/io/configurations/__init__.py +1 -0
  664. wbcore/contrib/io/configurations/base.py +7 -0
  665. wbcore/contrib/io/dynamic_preferences_registry.py +25 -0
  666. wbcore/contrib/io/enums.py +15 -0
  667. wbcore/contrib/io/exceptions.py +24 -0
  668. wbcore/contrib/io/factories.py +205 -0
  669. wbcore/contrib/io/fixtures/io.json +145 -0
  670. wbcore/contrib/io/import_export/backends/__init__.py +3 -0
  671. wbcore/contrib/io/import_export/backends/mail.py +51 -0
  672. wbcore/contrib/io/import_export/backends/sftp.py +69 -0
  673. wbcore/contrib/io/import_export/backends/stream.py +92 -0
  674. wbcore/contrib/io/import_export/parsers/__init__.py +0 -0
  675. wbcore/contrib/io/import_export/parsers/base_csv.py +36 -0
  676. wbcore/contrib/io/import_export/parsers/resources.py +50 -0
  677. wbcore/contrib/io/imports.py +313 -0
  678. wbcore/contrib/io/locale/de/LC_MESSAGES/django.po +144 -0
  679. wbcore/contrib/io/locale/de/LC_MESSAGES/django.po.translated +103 -0
  680. wbcore/contrib/io/locale/en/LC_MESSAGES/django.po +138 -0
  681. wbcore/contrib/io/locale/fr/LC_MESSAGES/django.po +138 -0
  682. wbcore/contrib/io/management/__init__.py +13 -0
  683. wbcore/contrib/io/migrations/0001_initial_squashed.py +319 -0
  684. wbcore/contrib/io/migrations/0002_importsource_creator.py +26 -0
  685. wbcore/contrib/io/migrations/0003_auto_20240103_1000.py +46 -0
  686. wbcore/contrib/io/migrations/0004_alter_importsource_status_exportsource.py +134 -0
  687. wbcore/contrib/io/migrations/0005_exportsource_data_alter_exportsource_query_str_and_more.py +67 -0
  688. wbcore/contrib/io/migrations/0006_alter_exportsource_query_params.py +20 -0
  689. wbcore/contrib/io/migrations/0007_alter_exportsource_query_params.py +23 -0
  690. wbcore/contrib/io/migrations/0008_importsource_resource_kwargs.py +18 -0
  691. wbcore/contrib/io/migrations/__init__.py +0 -0
  692. wbcore/contrib/io/mixins.py +37 -0
  693. wbcore/contrib/io/models.py +1037 -0
  694. wbcore/contrib/io/release_notes/1_0_0.md +13 -0
  695. wbcore/contrib/io/release_notes/__init__.py +0 -0
  696. wbcore/contrib/io/resources.py +176 -0
  697. wbcore/contrib/io/serializers.py +137 -0
  698. wbcore/contrib/io/signals.py +4 -0
  699. wbcore/contrib/io/tasks.py +27 -0
  700. wbcore/contrib/io/tests/__init__.py +0 -0
  701. wbcore/contrib/io/tests/conftest.py +42 -0
  702. wbcore/contrib/io/tests/test_backends.py +134 -0
  703. wbcore/contrib/io/tests/test_exports.py +130 -0
  704. wbcore/contrib/io/tests/test_imports.py +168 -0
  705. wbcore/contrib/io/tests/test_models.py +396 -0
  706. wbcore/contrib/io/tests/test_viewsets.py +271 -0
  707. wbcore/contrib/io/urls.py +29 -0
  708. wbcore/contrib/io/utils.py +44 -0
  709. wbcore/contrib/io/viewset_mixins.py +271 -0
  710. wbcore/contrib/io/viewsets.py +139 -0
  711. wbcore/contrib/notifications/__init__.py +0 -0
  712. wbcore/contrib/notifications/admin.py +63 -0
  713. wbcore/contrib/notifications/apps.py +43 -0
  714. wbcore/contrib/notifications/backends/__init__.py +0 -0
  715. wbcore/contrib/notifications/backends/abstract_backend.py +11 -0
  716. wbcore/contrib/notifications/backends/console/__init__.py +1 -0
  717. wbcore/contrib/notifications/backends/console/backends.py +25 -0
  718. wbcore/contrib/notifications/backends/firebase/__init__.py +1 -0
  719. wbcore/contrib/notifications/backends/firebase/backends.py +106 -0
  720. wbcore/contrib/notifications/configs.py +13 -0
  721. wbcore/contrib/notifications/configurations.py +9 -0
  722. wbcore/contrib/notifications/dispatch.py +90 -0
  723. wbcore/contrib/notifications/factories/__init__.py +0 -0
  724. wbcore/contrib/notifications/factories/notification_types.py +24 -0
  725. wbcore/contrib/notifications/factories/notifications.py +17 -0
  726. wbcore/contrib/notifications/factories/tokens.py +12 -0
  727. wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po +66 -0
  728. wbcore/contrib/notifications/locale/de/LC_MESSAGES/django.po.translated +63 -0
  729. wbcore/contrib/notifications/locale/en/LC_MESSAGES/django.po +61 -0
  730. wbcore/contrib/notifications/locale/fr/LC_MESSAGES/django.po +62 -0
  731. wbcore/contrib/notifications/migrations/0001_initial.py +116 -0
  732. wbcore/contrib/notifications/migrations/0002_notificationusertoken_unique_user_token_device.py +18 -0
  733. wbcore/contrib/notifications/migrations/0003_notificationusertoken_updated.py +17 -0
  734. wbcore/contrib/notifications/migrations/0004_alter_notification_body.py +17 -0
  735. wbcore/contrib/notifications/migrations/0005_alter_notification_endpoint.py +17 -0
  736. wbcore/contrib/notifications/migrations/0006_notification_created.py +25 -0
  737. wbcore/contrib/notifications/migrations/0007_notificationtype_resource_button_label.py +19 -0
  738. wbcore/contrib/notifications/migrations/0008_notificationtype_is_lock.py +18 -0
  739. wbcore/contrib/notifications/migrations/0009_alter_notificationtypesetting_options_and_more.py +32 -0
  740. wbcore/contrib/notifications/migrations/__init__.py +0 -0
  741. wbcore/contrib/notifications/models/__init__.py +3 -0
  742. wbcore/contrib/notifications/models/notification_types.py +165 -0
  743. wbcore/contrib/notifications/models/notifications.py +82 -0
  744. wbcore/contrib/notifications/models/tokens.py +46 -0
  745. wbcore/contrib/notifications/release_notes/1_0_0.md +13 -0
  746. wbcore/contrib/notifications/release_notes/__init__.py +0 -0
  747. wbcore/contrib/notifications/serializers/__init__.py +5 -0
  748. wbcore/contrib/notifications/serializers/notification_types.py +51 -0
  749. wbcore/contrib/notifications/serializers/notifications.py +33 -0
  750. wbcore/contrib/notifications/static/notifications/service-worker.js +1 -0
  751. wbcore/contrib/notifications/tasks.py +57 -0
  752. wbcore/contrib/notifications/templates/notifications/notification_template.html +43 -0
  753. wbcore/contrib/notifications/tests/__init__.py +0 -0
  754. wbcore/contrib/notifications/tests/conftest.py +47 -0
  755. wbcore/contrib/notifications/tests/test_backends/__init__.py +0 -0
  756. wbcore/contrib/notifications/tests/test_backends/test_firebase.py +79 -0
  757. wbcore/contrib/notifications/tests/test_configs.py +7 -0
  758. wbcore/contrib/notifications/tests/test_models/__init__.py +0 -0
  759. wbcore/contrib/notifications/tests/test_models/test_notification_types.py +85 -0
  760. wbcore/contrib/notifications/tests/test_models/test_notifications.py +46 -0
  761. wbcore/contrib/notifications/tests/test_models/test_tokens.py +30 -0
  762. wbcore/contrib/notifications/tests/test_serializers/__init__.py +0 -0
  763. wbcore/contrib/notifications/tests/test_serializers/test_notification_types.py +59 -0
  764. wbcore/contrib/notifications/tests/test_serializers/test_notifications.py +24 -0
  765. wbcore/contrib/notifications/tests/test_tasks.py +72 -0
  766. wbcore/contrib/notifications/tests/test_utils.py +0 -0
  767. wbcore/contrib/notifications/tests/test_viewsets/__init__.py +0 -0
  768. wbcore/contrib/notifications/tests/test_viewsets/test_notification_types.py +120 -0
  769. wbcore/contrib/notifications/tests/test_viewsets/test_notifications.py +143 -0
  770. wbcore/contrib/notifications/urls.py +27 -0
  771. wbcore/contrib/notifications/utils.py +27 -0
  772. wbcore/contrib/notifications/views.py +63 -0
  773. wbcore/contrib/notifications/viewsets/__init__.py +5 -0
  774. wbcore/contrib/notifications/viewsets/configs/notification_types.py +49 -0
  775. wbcore/contrib/notifications/viewsets/configs/notifications.py +91 -0
  776. wbcore/contrib/notifications/viewsets/menus.py +15 -0
  777. wbcore/contrib/notifications/viewsets/notification_types.py +51 -0
  778. wbcore/contrib/notifications/viewsets/notifications.py +58 -0
  779. wbcore/contrib/pandas/__init__.py +0 -0
  780. wbcore/contrib/pandas/fields.py +172 -0
  781. wbcore/contrib/pandas/filters.py +118 -0
  782. wbcore/contrib/pandas/filterset.py +28 -0
  783. wbcore/contrib/pandas/metadata.py +15 -0
  784. wbcore/contrib/pandas/tests/__init__.py +0 -0
  785. wbcore/contrib/pandas/tests/test_base.py +22 -0
  786. wbcore/contrib/pandas/tests/test_fields/__init__.py +0 -0
  787. wbcore/contrib/pandas/tests/test_fields/test_number_fields.py +19 -0
  788. wbcore/contrib/pandas/tests/test_filters/__init__.py +0 -0
  789. wbcore/contrib/pandas/tests/test_filters/test_pandas.py +115 -0
  790. wbcore/contrib/pandas/tests/test_views.py +22 -0
  791. wbcore/contrib/pandas/utils.py +170 -0
  792. wbcore/contrib/pandas/views.py +158 -0
  793. wbcore/contrib/tags/__init__.py +0 -0
  794. wbcore/contrib/tags/admin.py +17 -0
  795. wbcore/contrib/tags/apps.py +9 -0
  796. wbcore/contrib/tags/factories.py +28 -0
  797. wbcore/contrib/tags/filters.py +43 -0
  798. wbcore/contrib/tags/migrations/0001_initial.py +62 -0
  799. wbcore/contrib/tags/migrations/__init__.py +0 -0
  800. wbcore/contrib/tags/models/__init__.py +2 -0
  801. wbcore/contrib/tags/models/mixins.py +27 -0
  802. wbcore/contrib/tags/models/tags.py +107 -0
  803. wbcore/contrib/tags/release_notes/1_0_0.md +13 -0
  804. wbcore/contrib/tags/release_notes/__init__.py +0 -0
  805. wbcore/contrib/tags/serializers.py +90 -0
  806. wbcore/contrib/tags/signals.py +19 -0
  807. wbcore/contrib/tags/tests/__init__.py +0 -0
  808. wbcore/contrib/tags/tests/conftest.py +7 -0
  809. wbcore/contrib/tags/tests/tests.py +144 -0
  810. wbcore/contrib/tags/urls.py +15 -0
  811. wbcore/contrib/tags/viewsets/__init__.py +10 -0
  812. wbcore/contrib/tags/viewsets/display.py +50 -0
  813. wbcore/contrib/tags/viewsets/menu.py +15 -0
  814. wbcore/contrib/tags/viewsets/viewsets.py +58 -0
  815. wbcore/contrib/workflow/__init__.py +1 -0
  816. wbcore/contrib/workflow/admin/__init__.py +15 -0
  817. wbcore/contrib/workflow/admin/condition.py +9 -0
  818. wbcore/contrib/workflow/admin/data.py +15 -0
  819. wbcore/contrib/workflow/admin/display.py +9 -0
  820. wbcore/contrib/workflow/admin/process.py +37 -0
  821. wbcore/contrib/workflow/admin/step.py +92 -0
  822. wbcore/contrib/workflow/admin/transition.py +9 -0
  823. wbcore/contrib/workflow/admin/workflow.py +9 -0
  824. wbcore/contrib/workflow/apps.py +25 -0
  825. wbcore/contrib/workflow/configs.py +11 -0
  826. wbcore/contrib/workflow/decorators.py +25 -0
  827. wbcore/contrib/workflow/dispatch.py +24 -0
  828. wbcore/contrib/workflow/factories/__init__.py +18 -0
  829. wbcore/contrib/workflow/factories/condition.py +16 -0
  830. wbcore/contrib/workflow/factories/data.py +23 -0
  831. wbcore/contrib/workflow/factories/display.py +28 -0
  832. wbcore/contrib/workflow/factories/process.py +70 -0
  833. wbcore/contrib/workflow/factories/step.py +157 -0
  834. wbcore/contrib/workflow/factories/transition.py +27 -0
  835. wbcore/contrib/workflow/factories/workflow.py +20 -0
  836. wbcore/contrib/workflow/filters/__init__.py +24 -0
  837. wbcore/contrib/workflow/filters/condition.py +24 -0
  838. wbcore/contrib/workflow/filters/data.py +25 -0
  839. wbcore/contrib/workflow/filters/process.py +165 -0
  840. wbcore/contrib/workflow/filters/step.py +227 -0
  841. wbcore/contrib/workflow/filters/transition.py +42 -0
  842. wbcore/contrib/workflow/filters/workflow.py +44 -0
  843. wbcore/contrib/workflow/fixtures/workflow.json +612 -0
  844. wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po +1180 -0
  845. wbcore/contrib/workflow/locale/de/LC_MESSAGES/django.po.translated +1326 -0
  846. wbcore/contrib/workflow/locale/en/LC_MESSAGES/django.po +1102 -0
  847. wbcore/contrib/workflow/locale/fr/LC_MESSAGES/django.po +1114 -0
  848. wbcore/contrib/workflow/migrations/0001_initial.py +788 -0
  849. wbcore/contrib/workflow/migrations/0002_alter_step_step_type.py +31 -0
  850. wbcore/contrib/workflow/migrations/0003_alter_condition_attribute_name_and_more.py +75 -0
  851. wbcore/contrib/workflow/migrations/0004_alter_userstep_assignee_method.py +27 -0
  852. wbcore/contrib/workflow/migrations/0005_alter_userstep_assignee_method.py +17 -0
  853. wbcore/contrib/workflow/migrations/__init__.py +0 -0
  854. wbcore/contrib/workflow/models/__init__.py +19 -0
  855. wbcore/contrib/workflow/models/condition.py +124 -0
  856. wbcore/contrib/workflow/models/data.py +242 -0
  857. wbcore/contrib/workflow/models/display.py +34 -0
  858. wbcore/contrib/workflow/models/process.py +244 -0
  859. wbcore/contrib/workflow/models/step.py +778 -0
  860. wbcore/contrib/workflow/models/transition.py +71 -0
  861. wbcore/contrib/workflow/models/workflow.py +317 -0
  862. wbcore/contrib/workflow/serializers/__init__.py +37 -0
  863. wbcore/contrib/workflow/serializers/condition.py +65 -0
  864. wbcore/contrib/workflow/serializers/data.py +136 -0
  865. wbcore/contrib/workflow/serializers/display.py +25 -0
  866. wbcore/contrib/workflow/serializers/process.py +184 -0
  867. wbcore/contrib/workflow/serializers/signals.py +26 -0
  868. wbcore/contrib/workflow/serializers/step.py +365 -0
  869. wbcore/contrib/workflow/serializers/transition.py +81 -0
  870. wbcore/contrib/workflow/serializers/workflow.py +125 -0
  871. wbcore/contrib/workflow/sites.py +43 -0
  872. wbcore/contrib/workflow/static/workflow/markdown/documentation/assignedprocessstep.md +33 -0
  873. wbcore/contrib/workflow/static/workflow/markdown/documentation/condition.md +24 -0
  874. wbcore/contrib/workflow/static/workflow/markdown/documentation/decisionstep.md +30 -0
  875. wbcore/contrib/workflow/static/workflow/markdown/documentation/emailstep.md +45 -0
  876. wbcore/contrib/workflow/static/workflow/markdown/documentation/finishstep.md +33 -0
  877. wbcore/contrib/workflow/static/workflow/markdown/documentation/joinstep.md +33 -0
  878. wbcore/contrib/workflow/static/workflow/markdown/documentation/process.md +33 -0
  879. wbcore/contrib/workflow/static/workflow/markdown/documentation/processstep.md +51 -0
  880. wbcore/contrib/workflow/static/workflow/markdown/documentation/scriptstep.md +33 -0
  881. wbcore/contrib/workflow/static/workflow/markdown/documentation/splitstep.md +30 -0
  882. wbcore/contrib/workflow/static/workflow/markdown/documentation/startstep.md +27 -0
  883. wbcore/contrib/workflow/static/workflow/markdown/documentation/transition.md +27 -0
  884. wbcore/contrib/workflow/static/workflow/markdown/documentation/userstep.md +42 -0
  885. wbcore/contrib/workflow/static/workflow/markdown/documentation/workflow.md +32 -0
  886. wbcore/contrib/workflow/templates/Test_Templates.txt +25 -0
  887. wbcore/contrib/workflow/tests/__init__.py +0 -0
  888. wbcore/contrib/workflow/tests/conftest.py +281 -0
  889. wbcore/contrib/workflow/tests/test_configs.py +6 -0
  890. wbcore/contrib/workflow/tests/test_dispatch.py +94 -0
  891. wbcore/contrib/workflow/tests/test_displays.py +42 -0
  892. wbcore/contrib/workflow/tests/test_filters.py +104 -0
  893. wbcore/contrib/workflow/tests/test_models/step/test_decision_step.py +82 -0
  894. wbcore/contrib/workflow/tests/test_models/step/test_email_step.py +85 -0
  895. wbcore/contrib/workflow/tests/test_models/step/test_finish_step.py +167 -0
  896. wbcore/contrib/workflow/tests/test_models/step/test_join_step.py +117 -0
  897. wbcore/contrib/workflow/tests/test_models/step/test_script_step.py +24 -0
  898. wbcore/contrib/workflow/tests/test_models/step/test_split_step.py +49 -0
  899. wbcore/contrib/workflow/tests/test_models/step/test_step.py +756 -0
  900. wbcore/contrib/workflow/tests/test_models/step/test_user_step.py +225 -0
  901. wbcore/contrib/workflow/tests/test_models/test_condition.py +103 -0
  902. wbcore/contrib/workflow/tests/test_models/test_data.py +134 -0
  903. wbcore/contrib/workflow/tests/test_models/test_process.py +98 -0
  904. wbcore/contrib/workflow/tests/test_models/test_transition.py +128 -0
  905. wbcore/contrib/workflow/tests/test_models/test_workflow.py +358 -0
  906. wbcore/contrib/workflow/tests/test_serializers.py +200 -0
  907. wbcore/contrib/workflow/tests/test_viewsets.py +300 -0
  908. wbcore/contrib/workflow/tests/test_workflow_assignees.py +236 -0
  909. wbcore/contrib/workflow/urls.py +67 -0
  910. wbcore/contrib/workflow/utils.py +13 -0
  911. wbcore/contrib/workflow/viewsets/__init__.py +17 -0
  912. wbcore/contrib/workflow/viewsets/buttons/__init__.py +1 -0
  913. wbcore/contrib/workflow/viewsets/buttons/step.py +68 -0
  914. wbcore/contrib/workflow/viewsets/condition.py +32 -0
  915. wbcore/contrib/workflow/viewsets/data.py +33 -0
  916. wbcore/contrib/workflow/viewsets/display/__init__.py +15 -0
  917. wbcore/contrib/workflow/viewsets/display/condition.py +51 -0
  918. wbcore/contrib/workflow/viewsets/display/data.py +51 -0
  919. wbcore/contrib/workflow/viewsets/display/process.py +186 -0
  920. wbcore/contrib/workflow/viewsets/display/step.py +454 -0
  921. wbcore/contrib/workflow/viewsets/display/transition.py +76 -0
  922. wbcore/contrib/workflow/viewsets/display/workflow.py +168 -0
  923. wbcore/contrib/workflow/viewsets/endpoints/__init__.py +5 -0
  924. wbcore/contrib/workflow/viewsets/endpoints/condition.py +9 -0
  925. wbcore/contrib/workflow/viewsets/endpoints/data.py +9 -0
  926. wbcore/contrib/workflow/viewsets/endpoints/process.py +11 -0
  927. wbcore/contrib/workflow/viewsets/endpoints/step.py +24 -0
  928. wbcore/contrib/workflow/viewsets/endpoints/transition.py +19 -0
  929. wbcore/contrib/workflow/viewsets/menu/__init__.py +15 -0
  930. wbcore/contrib/workflow/viewsets/menu/condition.py +19 -0
  931. wbcore/contrib/workflow/viewsets/menu/data.py +19 -0
  932. wbcore/contrib/workflow/viewsets/menu/process.py +19 -0
  933. wbcore/contrib/workflow/viewsets/menu/step.py +124 -0
  934. wbcore/contrib/workflow/viewsets/menu/transition.py +19 -0
  935. wbcore/contrib/workflow/viewsets/menu/workflow.py +19 -0
  936. wbcore/contrib/workflow/viewsets/process.py +175 -0
  937. wbcore/contrib/workflow/viewsets/step.py +229 -0
  938. wbcore/contrib/workflow/viewsets/titles/__init__.py +16 -0
  939. wbcore/contrib/workflow/viewsets/titles/condition.py +14 -0
  940. wbcore/contrib/workflow/viewsets/titles/data.py +14 -0
  941. wbcore/contrib/workflow/viewsets/titles/process.py +27 -0
  942. wbcore/contrib/workflow/viewsets/titles/step.py +102 -0
  943. wbcore/contrib/workflow/viewsets/titles/transition.py +14 -0
  944. wbcore/contrib/workflow/viewsets/titles/workflow.py +14 -0
  945. wbcore/contrib/workflow/viewsets/transition.py +58 -0
  946. wbcore/contrib/workflow/viewsets/workflow.py +37 -0
  947. wbcore/contrib/workflow/workflows/__init__.py +1 -0
  948. wbcore/contrib/workflow/workflows/assignees.py +88 -0
  949. wbcore/crontab/__init__.py +0 -0
  950. wbcore/crontab/serializers.py +24 -0
  951. wbcore/crontab/viewsets.py +10 -0
  952. wbcore/dispatch.py +56 -0
  953. wbcore/docs/__init__.py +23 -0
  954. wbcore/docs/orderable.md +29 -0
  955. wbcore/docs/reparent.md +13 -0
  956. wbcore/dynamic_preferences_registry.py +131 -0
  957. wbcore/enums.py +50 -0
  958. wbcore/filters/__init__.py +21 -0
  959. wbcore/filters/backends.py +19 -0
  960. wbcore/filters/defaults.py +70 -0
  961. wbcore/filters/fields/__init__.py +15 -0
  962. wbcore/filters/fields/booleans.py +7 -0
  963. wbcore/filters/fields/choices.py +60 -0
  964. wbcore/filters/fields/content_type.py +45 -0
  965. wbcore/filters/fields/datetime.py +117 -0
  966. wbcore/filters/fields/models.py +123 -0
  967. wbcore/filters/fields/multiple_lookups.py +20 -0
  968. wbcore/filters/fields/numbers.py +48 -0
  969. wbcore/filters/fields/text.py +7 -0
  970. wbcore/filters/filterset.py +250 -0
  971. wbcore/filters/lookups.py +41 -0
  972. wbcore/filters/mixins.py +111 -0
  973. wbcore/filters/utils.py +21 -0
  974. wbcore/forms.py +125 -0
  975. wbcore/frontend.py +23 -0
  976. wbcore/frontend_user_configuration.py +96 -0
  977. wbcore/fsm/__init__.py +0 -0
  978. wbcore/fsm/markdown_extensions.py +31 -0
  979. wbcore/fsm/mixins.py +128 -0
  980. wbcore/locale/de/LC_MESSAGES/django.po +1252 -0
  981. wbcore/locale/de/LC_MESSAGES/django.po.translated +1580 -0
  982. wbcore/locale/en/LC_MESSAGES/django.po +1234 -0
  983. wbcore/locale/fr/LC_MESSAGES/django.po +1235 -0
  984. wbcore/management/__init__.py +88 -0
  985. wbcore/management/commands/__init__.py +0 -0
  986. wbcore/management/commands/bootstrap.py +18 -0
  987. wbcore/management/commands/clean_obsolete_object.py +21 -0
  988. wbcore/management/commands/handle_release_notes.py +54 -0
  989. wbcore/markdown/__init__.py +1 -0
  990. wbcore/markdown/admin.py +9 -0
  991. wbcore/markdown/dynamic_preferences_registry.py +16 -0
  992. wbcore/markdown/models.py +41 -0
  993. wbcore/markdown/template.py +38 -0
  994. wbcore/markdown/utils.py +37 -0
  995. wbcore/markdown/views.py +50 -0
  996. wbcore/menus/__init__.py +2 -0
  997. wbcore/menus/menus.py +96 -0
  998. wbcore/menus/registry.py +28 -0
  999. wbcore/menus/views.py +41 -0
  1000. wbcore/messages.py +60 -0
  1001. wbcore/metadata/__init__.py +0 -0
  1002. wbcore/metadata/configs/__init__.py +0 -0
  1003. wbcore/metadata/configs/base.py +86 -0
  1004. wbcore/metadata/configs/buttons/__init__.py +4 -0
  1005. wbcore/metadata/configs/buttons/bases.py +89 -0
  1006. wbcore/metadata/configs/buttons/buttons.py +138 -0
  1007. wbcore/metadata/configs/buttons/enums.py +68 -0
  1008. wbcore/metadata/configs/buttons/metadata.py +8 -0
  1009. wbcore/metadata/configs/buttons/view_config.py +145 -0
  1010. wbcore/metadata/configs/display/__init__.py +4 -0
  1011. wbcore/metadata/configs/display/configs.py +16 -0
  1012. wbcore/metadata/configs/display/display.py +234 -0
  1013. wbcore/metadata/configs/display/formatting.py +47 -0
  1014. wbcore/metadata/configs/display/instance_display/__init__.py +15 -0
  1015. wbcore/metadata/configs/display/instance_display/display.py +40 -0
  1016. wbcore/metadata/configs/display/instance_display/enums.py +7 -0
  1017. wbcore/metadata/configs/display/instance_display/layouts/__init__.py +3 -0
  1018. wbcore/metadata/configs/display/instance_display/layouts/inlines.py +58 -0
  1019. wbcore/metadata/configs/display/instance_display/layouts/layouts.py +77 -0
  1020. wbcore/metadata/configs/display/instance_display/layouts/sections.py +43 -0
  1021. wbcore/metadata/configs/display/instance_display/operators.py +22 -0
  1022. wbcore/metadata/configs/display/instance_display/pages.py +40 -0
  1023. wbcore/metadata/configs/display/instance_display/shortcuts.py +81 -0
  1024. wbcore/metadata/configs/display/instance_display/signals.py +3 -0
  1025. wbcore/metadata/configs/display/instance_display/styles.py +42 -0
  1026. wbcore/metadata/configs/display/instance_display/utils.py +74 -0
  1027. wbcore/metadata/configs/display/list_display.py +290 -0
  1028. wbcore/metadata/configs/display/models.py +26 -0
  1029. wbcore/metadata/configs/display/view_config.py +85 -0
  1030. wbcore/metadata/configs/display/views.py +48 -0
  1031. wbcore/metadata/configs/display/windows.py +28 -0
  1032. wbcore/metadata/configs/documentations.py +11 -0
  1033. wbcore/metadata/configs/endpoints.py +189 -0
  1034. wbcore/metadata/configs/fields.py +22 -0
  1035. wbcore/metadata/configs/filter_fields.py +41 -0
  1036. wbcore/metadata/configs/identifiers.py +27 -0
  1037. wbcore/metadata/configs/ordering_fields.py +24 -0
  1038. wbcore/metadata/configs/paginations.py +11 -0
  1039. wbcore/metadata/configs/preview.py +41 -0
  1040. wbcore/metadata/configs/primary_keys.py +9 -0
  1041. wbcore/metadata/configs/search_fields.py +9 -0
  1042. wbcore/metadata/configs/titles.py +48 -0
  1043. wbcore/metadata/configs/window_types.py +13 -0
  1044. wbcore/metadata/exceptions.py +0 -0
  1045. wbcore/metadata/metadata.py +34 -0
  1046. wbcore/metadata/mixins.py +106 -0
  1047. wbcore/metadata/tests/__init__.py +0 -0
  1048. wbcore/metadata/tests/test_buttons.py +165 -0
  1049. wbcore/metadata/utils.py +4 -0
  1050. wbcore/migrations/0001_initial_squashed_squashed_0010_preset_appliedpreset.py +398 -0
  1051. wbcore/migrations/0011_genericmodel.py +22 -0
  1052. wbcore/migrations/0012_delete_notification.py +15 -0
  1053. wbcore/migrations/0013_delete_colorgradient.py +14 -0
  1054. wbcore/migrations/0014_biguserobjectpermission_system.py +44 -0
  1055. wbcore/migrations/__init__.py +0 -0
  1056. wbcore/models/__init__.py +6 -0
  1057. wbcore/models/base.py +184 -0
  1058. wbcore/models/fields.py +29 -0
  1059. wbcore/models/orderable.py +6 -0
  1060. wbcore/pagination.py +64 -0
  1061. wbcore/pandas/__init__.py +53 -0
  1062. wbcore/pandas/fields.py +25 -0
  1063. wbcore/pandas/filterset.py +9 -0
  1064. wbcore/pandas/utils.py +25 -0
  1065. wbcore/pandas/views.py +9 -0
  1066. wbcore/permissions/__init__.py +0 -0
  1067. wbcore/permissions/backend.py +36 -0
  1068. wbcore/permissions/mixins.py +72 -0
  1069. wbcore/permissions/permissions.py +51 -0
  1070. wbcore/permissions/registry.py +33 -0
  1071. wbcore/permissions/shortcuts.py +38 -0
  1072. wbcore/permissions/utils.py +26 -0
  1073. wbcore/release_notes/__init__.py +0 -0
  1074. wbcore/release_notes/admin.py +30 -0
  1075. wbcore/release_notes/buttons.py +26 -0
  1076. wbcore/release_notes/display.py +52 -0
  1077. wbcore/release_notes/filters.py +36 -0
  1078. wbcore/release_notes/models.py +66 -0
  1079. wbcore/release_notes/serializers.py +12 -0
  1080. wbcore/release_notes/utils.py +14 -0
  1081. wbcore/release_notes/viewsets.py +52 -0
  1082. wbcore/reversion/__init__.py +0 -0
  1083. wbcore/reversion/filters.py +39 -0
  1084. wbcore/reversion/serializers.py +81 -0
  1085. wbcore/reversion/viewsets/__init__.py +6 -0
  1086. wbcore/reversion/viewsets/buttons.py +95 -0
  1087. wbcore/reversion/viewsets/displays.py +45 -0
  1088. wbcore/reversion/viewsets/endpoints.py +30 -0
  1089. wbcore/reversion/viewsets/titles.py +19 -0
  1090. wbcore/reversion/viewsets/viewsets.py +112 -0
  1091. wbcore/routers.py +63 -0
  1092. wbcore/search/__init__.py +62 -0
  1093. wbcore/serializers/__init__.py +68 -0
  1094. wbcore/serializers/fields/__init__.py +59 -0
  1095. wbcore/serializers/fields/boolean.py +51 -0
  1096. wbcore/serializers/fields/choice.py +60 -0
  1097. wbcore/serializers/fields/datetime.py +178 -0
  1098. wbcore/serializers/fields/fields.py +193 -0
  1099. wbcore/serializers/fields/file.py +20 -0
  1100. wbcore/serializers/fields/fsm.py +20 -0
  1101. wbcore/serializers/fields/json.py +56 -0
  1102. wbcore/serializers/fields/list.py +104 -0
  1103. wbcore/serializers/fields/mixins.py +196 -0
  1104. wbcore/serializers/fields/number.py +94 -0
  1105. wbcore/serializers/fields/other.py +34 -0
  1106. wbcore/serializers/fields/primary_key.py +22 -0
  1107. wbcore/serializers/fields/related.py +150 -0
  1108. wbcore/serializers/fields/text.py +141 -0
  1109. wbcore/serializers/fields/types.py +46 -0
  1110. wbcore/serializers/mixins.py +24 -0
  1111. wbcore/serializers/serializers.py +425 -0
  1112. wbcore/serializers/utils.py +143 -0
  1113. wbcore/shares/__init__.py +1 -0
  1114. wbcore/shares/config.py +64 -0
  1115. wbcore/shares/decorator.py +13 -0
  1116. wbcore/shares/signals.py +3 -0
  1117. wbcore/shares/sites.py +29 -0
  1118. wbcore/shares/views.py +25 -0
  1119. wbcore/signals/__init__.py +7 -0
  1120. wbcore/signals/clone.py +4 -0
  1121. wbcore/signals/filters.py +3 -0
  1122. wbcore/signals/instance_buttons.py +5 -0
  1123. wbcore/signals/merge.py +4 -0
  1124. wbcore/signals/models.py +4 -0
  1125. wbcore/signals/permissions.py +3 -0
  1126. wbcore/signals/serializers.py +5 -0
  1127. wbcore/tasks.py +87 -0
  1128. wbcore/template.py +29 -0
  1129. wbcore/templates/errors/404.html +134 -0
  1130. wbcore/templates/errors/500.html +138 -0
  1131. wbcore/templates/errors/503.html +132 -0
  1132. wbcore/templates/errors/custom.html +132 -0
  1133. wbcore/templates/forms.py +0 -0
  1134. wbcore/templates/notifications/email_template.html +43 -0
  1135. wbcore/templates/reversion/__init__.py +0 -0
  1136. wbcore/templates/reversion/compare_detail.html +19 -0
  1137. wbcore/templates/wbcore/admin/change_list.html +8 -0
  1138. wbcore/templates/wbcore/admin/csv_form.html +15 -0
  1139. wbcore/templates/wbcore/dynamic_color_array.html +29 -0
  1140. wbcore/templates/wbcore/email_base_template.html +335 -0
  1141. wbcore/templates/wbcore/email_notification_template.html +10 -0
  1142. wbcore/templates/wbcore/frontend.html +51 -0
  1143. wbcore/test/__init__.py +30 -0
  1144. wbcore/test/e2e_helpers_methods/e2e_checks.py +127 -0
  1145. wbcore/test/e2e_helpers_methods/e2e_helper_methods.py +397 -0
  1146. wbcore/test/mixins.py +660 -0
  1147. wbcore/test/signals.py +6 -0
  1148. wbcore/test/tests.py +129 -0
  1149. wbcore/test/utils.py +227 -0
  1150. wbcore/tests/__init__.py +0 -0
  1151. wbcore/tests/conftest.py +55 -0
  1152. wbcore/tests/e2e/__init__.py +0 -0
  1153. wbcore/tests/e2e/test_e2e.py +25 -0
  1154. wbcore/tests/models.py +6 -0
  1155. wbcore/tests/test_cache/__init__.py +0 -0
  1156. wbcore/tests/test_cache/test_decorators.py +28 -0
  1157. wbcore/tests/test_cache/test_mixins.py +30 -0
  1158. wbcore/tests/test_cache/test_registry.py +58 -0
  1159. wbcore/tests/test_configs.py +58 -0
  1160. wbcore/tests/test_enums.py +56 -0
  1161. wbcore/tests/test_fields/__init__.py +0 -0
  1162. wbcore/tests/test_fields/test_boolean_fields.py +49 -0
  1163. wbcore/tests/test_fields/test_choice_fields.py +57 -0
  1164. wbcore/tests/test_fields/test_datetime_fields.py +152 -0
  1165. wbcore/tests/test_fields/test_fields.py +24 -0
  1166. wbcore/tests/test_fields/test_file_fields.py +52 -0
  1167. wbcore/tests/test_fields/test_json_fields.py +28 -0
  1168. wbcore/tests/test_fields/test_list_fields.py +27 -0
  1169. wbcore/tests/test_fields/test_mixins.py +112 -0
  1170. wbcore/tests/test_fields/test_number_fields.py +166 -0
  1171. wbcore/tests/test_fields/test_other_fields.py +60 -0
  1172. wbcore/tests/test_fields/test_primary_key_fields.py +52 -0
  1173. wbcore/tests/test_fields/test_related.py +81 -0
  1174. wbcore/tests/test_fields/test_text_fields.py +118 -0
  1175. wbcore/tests/test_filters/__init__.py +0 -0
  1176. wbcore/tests/test_filters/test_mixins.py +109 -0
  1177. wbcore/tests/test_list_display.py +29 -0
  1178. wbcore/tests/test_models/__init__.py +0 -0
  1179. wbcore/tests/test_models/test_fields.py +26 -0
  1180. wbcore/tests/test_models/test_mixins.py +32 -0
  1181. wbcore/tests/test_new_display/__init__.py +0 -0
  1182. wbcore/tests/test_new_display/test_inlines.py +13 -0
  1183. wbcore/tests/test_new_display/test_layouts.py +15 -0
  1184. wbcore/tests/test_new_display/test_operators.py +16 -0
  1185. wbcore/tests/test_new_display/test_pages.py +8 -0
  1186. wbcore/tests/test_new_display/test_sections.py +0 -0
  1187. wbcore/tests/test_new_display/test_shortcuts.py +38 -0
  1188. wbcore/tests/test_new_display/test_utils.py +49 -0
  1189. wbcore/tests/test_pagination.py +32 -0
  1190. wbcore/tests/test_permissions/test_backend.py +29 -0
  1191. wbcore/tests/test_serializers/__init__.py +0 -0
  1192. wbcore/tests/test_serializers/test_fields.py +141 -0
  1193. wbcore/tests/test_serializers/test_mixins.py +54 -0
  1194. wbcore/tests/test_serializers/test_related.py +78 -0
  1195. wbcore/tests/test_something.py +39 -0
  1196. wbcore/tests/test_utils/__init__.py +0 -0
  1197. wbcore/tests/test_utils/test_date.py +50 -0
  1198. wbcore/tests/test_utils/test_date_builder.py +124 -0
  1199. wbcore/tests/test_utils/test_primary.py +80 -0
  1200. wbcore/tests/test_utils/test_signals.py +40 -0
  1201. wbcore/tests/test_viewsets.py +21 -0
  1202. wbcore/urls.py +117 -0
  1203. wbcore/utils/__init__.py +13 -0
  1204. wbcore/utils/cache.py +8 -0
  1205. wbcore/utils/date.py +237 -0
  1206. wbcore/utils/date_builder/__init__.py +18 -0
  1207. wbcore/utils/date_builder/components.py +42 -0
  1208. wbcore/utils/date_builder/offsets.py +27 -0
  1209. wbcore/utils/deprecations.py +11 -0
  1210. wbcore/utils/enum.py +23 -0
  1211. wbcore/utils/figures.py +286 -0
  1212. wbcore/utils/html.py +8 -0
  1213. wbcore/utils/importlib.py +13 -0
  1214. wbcore/utils/itertools.py +40 -0
  1215. wbcore/utils/models.py +293 -0
  1216. wbcore/utils/numbers.py +69 -0
  1217. wbcore/utils/prettytable.py +35 -0
  1218. wbcore/utils/print.py +29 -0
  1219. wbcore/utils/renderers.py +13 -0
  1220. wbcore/utils/reportlab.py +7 -0
  1221. wbcore/utils/rrules.py +82 -0
  1222. wbcore/utils/serializers.py +9 -0
  1223. wbcore/utils/settings.py +5 -0
  1224. wbcore/utils/signals.py +36 -0
  1225. wbcore/utils/string_loader.py +42 -0
  1226. wbcore/utils/strings.py +77 -0
  1227. wbcore/utils/urls.py +70 -0
  1228. wbcore/utils/views.py +207 -0
  1229. wbcore/views.py +26 -0
  1230. wbcore/viewsets/__init__.py +12 -0
  1231. wbcore/viewsets/encoders.py +18 -0
  1232. wbcore/viewsets/generics.py +5 -0
  1233. wbcore/viewsets/mixins.py +295 -0
  1234. wbcore/viewsets/utils.py +27 -0
  1235. wbcore/viewsets/viewsets.py +202 -0
  1236. wbcore/workers.py +8 -0
  1237. wbcore-1.59.9.dist-info/METADATA +65 -0
  1238. wbcore-1.59.9.dist-info/RECORD +1239 -0
  1239. wbcore-1.59.9.dist-info/WHEEL +5 -0
@@ -0,0 +1,1037 @@
1
+ import importlib
2
+ import io
3
+ import json
4
+ import logging
5
+ import os
6
+ import sys
7
+ import traceback
8
+ import uuid
9
+ from contextlib import suppress
10
+ from copy import deepcopy
11
+ from datetime import datetime
12
+ from importlib import import_module
13
+ from typing import Any, Callable, Dict, Optional
14
+
15
+ import magic
16
+ from celery import chain, shared_task
17
+ from croniter import croniter
18
+ from django.apps import apps
19
+ from django.conf import settings
20
+ from django.contrib.contenttypes.fields import GenericForeignKey
21
+ from django.contrib.contenttypes.models import ContentType
22
+ from django.core.exceptions import ValidationError
23
+ from django.core.files.base import ContentFile
24
+ from django.db import models, transaction
25
+ from django.db.models import Q
26
+ from django.db.models.signals import post_delete, pre_delete
27
+ from django.db.utils import IntegrityError
28
+ from django.dispatch import receiver
29
+ from django.utils import timezone
30
+ from django.utils.functional import cached_property
31
+ from django.utils.translation import gettext_lazy as _
32
+ from django_celery_beat.models import CrontabSchedule, PeriodicTask, cronexp
33
+ from import_export.resources import Resource
34
+ from picklefield.fields import PickledObjectField
35
+ from tablib import Dataset
36
+ from tqdm import tqdm
37
+
38
+ from wbcore.contrib.notifications.dispatch import send_notification
39
+ from wbcore.contrib.notifications.utils import create_notification_type
40
+ from wbcore.utils.importlib import import_from_dotted_path
41
+ from wbcore.utils.models import ComplexToStringMixin
42
+
43
+ from ...workers import Queue
44
+ from .enums import ExportFormat, get_django_import_export_format
45
+ from .signals import post_import
46
+
47
+ logger = logging.getLogger("io")
48
+
49
+
50
+ class ParserHandler(models.Model):
51
+ class MimeTypeChoices(models.TextChoices):
52
+ CSV = "text/csv", "CSV"
53
+ JSON = "application/json", "JSON"
54
+ XLS = "application/vnd.ms-excel", "XLS"
55
+ XLSX = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "XLSX"
56
+
57
+ parser = models.CharField(max_length=255)
58
+ handler = models.CharField(max_length=255)
59
+
60
+ allow_file_type = models.CharField(max_length=68, blank=True, null=True, choices=MimeTypeChoices.choices)
61
+
62
+ def __str__(self) -> str:
63
+ return f"{self.parser}::{self.handler}"
64
+
65
+ @cached_property
66
+ def model(self):
67
+ return apps.get_model(self.handler)
68
+
69
+ def parse(self, import_source: "ImportSource") -> Dict[str, Any]:
70
+ """
71
+ call the parser on the provided data
72
+ Args:
73
+ import_source: The import source containing the data
74
+ Returns:
75
+ The parsed data
76
+ """
77
+
78
+ parser = importlib.import_module(self.parser)
79
+ return parser.parse(import_source)
80
+
81
+ def handle(self, import_source: "ImportSource", parsed_data: Dict[str, Any], **kwargs):
82
+ """
83
+ Call the Handler on the parsed data
84
+ Args:
85
+ import_source: The initial import source
86
+ parsed_data: The parsed_data
87
+ """
88
+ if parsed_data:
89
+ if handler_class := getattr(self.model, "import_export_handler_class", None):
90
+ handler = handler_class(import_source)
91
+ handler.process(parsed_data, **kwargs)
92
+
93
+ class Meta:
94
+ unique_together = ("parser", "handler")
95
+ verbose_name = _("Parser-Handler")
96
+ verbose_name_plural = _("Parsers-Handlers")
97
+
98
+ @classmethod
99
+ def get_representation_value_key(cls) -> str:
100
+ return "id"
101
+
102
+ @classmethod
103
+ def get_representation_label_key(cls) -> str:
104
+ return "{{parser}}::{{handler}}"
105
+
106
+ @classmethod
107
+ def get_representation_endpoint(cls) -> str:
108
+ return "wbcore:io:parserhandlerrepresentation-list"
109
+
110
+
111
+ class ImportedObjectProviderRelationship(ComplexToStringMixin):
112
+ """
113
+ A model that represent the relationship/link between the imported object and a provider.
114
+
115
+ This model can be used to define different identifier in case the object is imported from difference providers
116
+ """
117
+
118
+ provider = models.ForeignKey(
119
+ to="io.Provider", related_name="imported_object_relationships", on_delete=models.CASCADE
120
+ )
121
+ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
122
+ object_id = models.PositiveIntegerField()
123
+ content_object = GenericForeignKey("content_type", "object_id")
124
+ provider_identifier = models.CharField(max_length=255)
125
+
126
+ def compute_str(self) -> str:
127
+ try:
128
+ return f"{self.provider.title} -> {self.content_object}: {self.provider_identifier}"
129
+ except AttributeError:
130
+ return f"{self.provider.title} ({self.provider_identifier})"
131
+
132
+ class Meta:
133
+ verbose_name = _("Content object Provider Identifier relationship")
134
+ verbose_name_plural = _("Content object Provider Identifier relationships")
135
+ unique_together = [
136
+ ("content_type", "object_id", "provider"),
137
+ ("content_type", "provider_identifier", "provider"),
138
+ ]
139
+ indexes = [
140
+ models.Index(fields=["content_type", "object_id", "provider"]),
141
+ models.Index(fields=["content_type", "provider_identifier", "provider"]),
142
+ ]
143
+
144
+
145
+ class Provider(models.Model):
146
+ """
147
+ Represent a data vendor/provider. Every source is linked to a provider and are represented by a unique identifier
148
+ extracted from the data backend root folder
149
+
150
+ Example:
151
+ io/backend/refinitiv/instrument_prices.py => Provider.objects.get(key="refinitiv")
152
+ """
153
+
154
+ title = models.CharField(max_length=255)
155
+ key = models.CharField(max_length=255, unique=True)
156
+
157
+ def __str__(self) -> str:
158
+ return f"{self.title} ({self.key})"
159
+
160
+ class Meta:
161
+ verbose_name = _("Provider")
162
+ verbose_name_plural = _("Providers")
163
+
164
+
165
+ class DataBackend(models.Model):
166
+ """
167
+ Represents the instantiated backend imported through the specified dotted path and the passed parameters
168
+ """
169
+
170
+ title = models.CharField(max_length=255)
171
+
172
+ save_data_in_import_source = models.BooleanField(
173
+ default=True, help_text="If true, save the data in the import_source json field"
174
+ )
175
+ passive_only = models.BooleanField(
176
+ default=True, help_text="If True, this data backend is allowed to be called only from the import source."
177
+ )
178
+
179
+ backend_class_path = models.CharField(max_length=512)
180
+ backend_class_name = models.CharField(max_length=128, default="DataBackend")
181
+
182
+ provider = models.ForeignKey(
183
+ "io.Provider",
184
+ on_delete=models.SET_NULL,
185
+ null=True,
186
+ blank=True,
187
+ verbose_name=_("Provider"),
188
+ )
189
+
190
+ def get_internal_object(self, object_content_type: ContentType, provider_external_id: str) -> models.Model | None:
191
+ """
192
+ Internal facing function that returns the internal id of the object with the given external provider identifier. If the relationship already exists, we simply returns the stored id. Otherwise we call the appropriate function to get
193
+ Args:
194
+ object_content_type: ContentType object representing the seeked object id
195
+ provider_external_id: The object identifier as given by this backend provider
196
+
197
+ Returns:
198
+ The object as stored in the importer object-provider relationship. None if it doesn't exist yet
199
+ """
200
+ if rel := ImportedObjectProviderRelationship.objects.filter(
201
+ provider=self.provider, provider_identifier=provider_external_id, content_type=object_content_type
202
+ ).first():
203
+ return rel.content_object
204
+
205
+ class Meta:
206
+ verbose_name = _("Data Backend")
207
+ verbose_name_plural = _("Data Backends")
208
+
209
+ @cached_property
210
+ def backend_class(self) -> Any:
211
+ """
212
+ Return the imported backend class
213
+ Returns:
214
+ The backend class
215
+ """
216
+ return getattr(import_module(self.backend_class_path), self.backend_class_name)
217
+
218
+ def __str__(self) -> str:
219
+ return self.title
220
+
221
+ @classmethod
222
+ def get_representation_value_key(cls) -> str:
223
+ return "id"
224
+
225
+ @classmethod
226
+ def get_representation_label_key(cls) -> str:
227
+ return "{{title}}"
228
+
229
+ @classmethod
230
+ def get_representation_endpoint(cls) -> str:
231
+ return "wbcore:io:databackendrepresentation-list"
232
+
233
+
234
+ class Source(models.Model):
235
+ title = models.CharField(max_length=128)
236
+ uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
237
+ parser_handler = models.ManyToManyField(
238
+ "io.ParserHandler",
239
+ verbose_name="Parser/Handler",
240
+ related_name="sources",
241
+ )
242
+
243
+ is_active = models.BooleanField(default=True)
244
+
245
+ connection_parameters = models.JSONField(default=dict, null=True, blank=True)
246
+ import_parameters = models.JSONField(default=dict, null=True, blank=True)
247
+
248
+ credentials = models.ManyToManyField("io.ImportCredential", blank=True, related_name="sources")
249
+
250
+ data_backend = models.ForeignKey("io.DataBackend", on_delete=models.CASCADE, verbose_name=_("Data Backend"))
251
+
252
+ crontab = models.ForeignKey(
253
+ CrontabSchedule,
254
+ on_delete=models.SET_NULL,
255
+ null=True,
256
+ blank=True,
257
+ verbose_name=_("Crontab Schedule"),
258
+ help_text=_("Crontab Schedule to run the task on. Set only one schedule type, leave the others null."),
259
+ )
260
+
261
+ periodic_task = models.ForeignKey(
262
+ PeriodicTask,
263
+ on_delete=models.SET_NULL,
264
+ null=True,
265
+ blank=True,
266
+ verbose_name="Periodic Task",
267
+ related_name="sources",
268
+ )
269
+
270
+ import_timedelta_interval = models.IntegerField(default=0)
271
+
272
+ def __init__(self, *args, **kwargs):
273
+ super().__init__(*args, **kwargs)
274
+ self.instantiated_backend: Any = None
275
+
276
+ def init_backend(self, execution_datetime: datetime):
277
+ """
278
+ Singleton that instantiate the backend attribute
279
+
280
+ Args:
281
+ execution_datetime: Datetime at which the backend is supposed to be initialize
282
+
283
+ """
284
+ if not self.instantiated_backend:
285
+ self.instantiated_backend = self.data_backend.backend_class(
286
+ import_credential=self.get_valid_credential(execution_datetime),
287
+ data_backend=self.data_backend,
288
+ **self.connection_parameters,
289
+ )
290
+
291
+ def save(self, *args, **kwargs):
292
+ super().save(*args, **kwargs)
293
+ self.update_periodic_task()
294
+
295
+ def __str__(self) -> str:
296
+ return self.title if self.title else f"{self.uuid}"
297
+
298
+ class Meta:
299
+ verbose_name = _("Source")
300
+ verbose_name_plural = _("Sources")
301
+
302
+ @property
303
+ def current_valid_credential(self) -> "ImportCredential":
304
+ """
305
+ Property that returns the current valid credential
306
+
307
+ Returns:
308
+ The valid ImportCredential (as of now)
309
+ """
310
+ return self.get_valid_credential(timezone.now())
311
+
312
+ @property
313
+ def crontab_repr(self) -> str:
314
+ """
315
+ Returns the crontab string representation.
316
+ :return: crontab string representation
317
+ """
318
+ if not self.crontab:
319
+ return ""
320
+
321
+ return "{0} {1} {2} {3} {4}".format(
322
+ cronexp(self.crontab.minute),
323
+ cronexp(self.crontab.hour),
324
+ cronexp(self.crontab.day_of_month),
325
+ cronexp(self.crontab.month_of_year),
326
+ cronexp(self.crontab.day_of_week),
327
+ )
328
+
329
+ def update_periodic_task(self):
330
+ """
331
+ Utility function to find and update the link periodic task given the current source state
332
+ """
333
+ if self.crontab:
334
+ if task := self.periodic_task:
335
+ task.crontab = self.crontab
336
+ task.enabled = self.is_active
337
+ task.save()
338
+ else:
339
+ task = PeriodicTask.objects.update_or_create(
340
+ name=f"Import-Export: {self}",
341
+ defaults={
342
+ "crontab": self.crontab,
343
+ "args": json.dumps([self.pk]),
344
+ "task": "wbcore.contrib.io.models.trigger_workflow_as_task",
345
+ },
346
+ )[0]
347
+ Source.objects.filter(pk=self.pk).update(periodic_task=task)
348
+ self.refresh_from_db()
349
+
350
+ def get_valid_credential(self, val_datetime: datetime) -> "ImportCredential":
351
+ """
352
+ Get the valid credential
353
+ Args:
354
+ val_datetime: The date at which the credentials need to be valid
355
+ Returns:
356
+ A valid credential
357
+ """
358
+ return (
359
+ self.credentials.filter(
360
+ (Q(validity_start__lte=val_datetime) | Q(validity_start__isnull=True))
361
+ & (Q(validity_end__gte=val_datetime) | Q(validity_end__isnull=True))
362
+ )
363
+ .order_by("-validity_end")
364
+ .first()
365
+ )
366
+
367
+ def get_or_create_provider_id(self, obj: models.Model) -> str | None:
368
+ """
369
+ Workflow method that try to get or fetch the provider identifier for the passed object.
370
+
371
+ If the relationship already exists, we directly returns the stored external id. Otherwise we call the appropriate function on the already instantiated backend to get it from the provider itself. If the value is returned, we store the relationship
372
+
373
+ Args:
374
+ obj: The object to get the external provider identifier from
375
+
376
+ Returns:
377
+ If it exists, the external identifier (from this source provider). None otherwise.
378
+ """
379
+
380
+ if (provider := self.data_backend.provider) and self.instantiated_backend is not None:
381
+ if self.instantiated_backend.is_object_valid(obj):
382
+ if rel := ImportedObjectProviderRelationship.objects.filter(
383
+ provider=provider, content_type=ContentType.objects.get_for_model(obj), object_id=obj.pk
384
+ ).first():
385
+ return rel.provider_identifier
386
+ else:
387
+ if provider_identifier := self.instantiated_backend.get_provider_id(obj):
388
+ with transaction.atomic():
389
+ ImportedObjectProviderRelationship.objects.create(
390
+ provider=provider,
391
+ content_type=ContentType.objects.get_for_model(obj),
392
+ object_id=obj.pk,
393
+ provider_identifier=provider_identifier,
394
+ )
395
+ return provider_identifier
396
+ obj.save()
397
+ else:
398
+ raise ValueError(
399
+ f"You can't create a provider relationship with {obj} with for backend {self.data_backend} without a provider assigned"
400
+ )
401
+
402
+ def is_valid_date(self, sync_datetime: datetime) -> bool:
403
+ """
404
+ check wether a date is valid given the stored crontab schedule
405
+ Args:
406
+ sync_datetime: The datetime at which validity needs to be checked
407
+ Returns:
408
+ True if the given date is valid given the source crontab
409
+ """
410
+ if not self.crontab:
411
+ return False
412
+ return croniter.match(self.crontab_repr, sync_datetime)
413
+
414
+ def trigger_workflow(
415
+ self,
416
+ execution_time: Optional[datetime] = None,
417
+ callback_signature: Callable | None = None,
418
+ callback_args: list | None = None,
419
+ callback_kwargs: Dict | None = None,
420
+ synchronous: bool = False,
421
+ **kwargs,
422
+ ):
423
+ """
424
+ The entry point function of the whole import source workflow.
425
+ Loop over all files return by the attached backend and save the result into a serie of import sources.
426
+
427
+ Args:
428
+ execution_time: The time at which the worfklow was triggerd
429
+ callback_signature: The celery canvas signature to be called upon completion (if any)
430
+ callback_args: the potential positional arguments to be passed to the callback function (if any)
431
+ callback_kwargs: Potential keyword arguments to be passed to the callback function (if any)
432
+ synchronous: If true, will execute the workflow synchronously. Default to False
433
+ **kwargs: keyword arguments to be passed down along the workflow
434
+
435
+ Returns:
436
+ A generator of import sources
437
+ """
438
+ if not execution_time:
439
+ execution_time = timezone.now()
440
+ # If a queryset is provided, we need to convert it into a list of valid external id that we will get from the relationship through model or from the backend directly if not existing
441
+
442
+ chained_tasks = [
443
+ generate_import_sources_as_task.s(self.pk, execution_time, **kwargs),
444
+ process_import_sources_as_task.s(),
445
+ ]
446
+ if not callback_args:
447
+ callback_args = []
448
+ if not callback_kwargs:
449
+ callback_kwargs = {}
450
+ if callback_signature:
451
+ chained_tasks.append(callback_signature.si(*callback_args, **callback_kwargs))
452
+ if synchronous:
453
+ import_source_ids = chained_tasks[0].apply().get()
454
+ chained_tasks[1].apply((import_source_ids,))
455
+ if len(chained_tasks) > 2:
456
+ chained_tasks[2].apply(tuple(callback_args), callback_kwargs)
457
+ else:
458
+ chain(*chained_tasks).apply_async(eta=execution_time)
459
+
460
+ def generate_import_sources(
461
+ self,
462
+ execution_time: datetime,
463
+ only_handler: str | None = None,
464
+ **kwargs,
465
+ ) -> list["ImportSource"]:
466
+ """
467
+ Process the source based on its attached backend (returns an error if such backend does not exist)
468
+ The backend is first initialize through constructor and then, The generator `get_files` is called, passing the
469
+ execution time, the expected data time, and some keyword arguments.
470
+
471
+ For each file returns by the generator, we create a ImportSource objects and trigger its import task
472
+ Args:
473
+ execution_time: The time at which this task was triggered
474
+ **kwargs: keyword arguments to be passed down along the workflow
475
+ Returns:
476
+ Return a generator of import sources
477
+ """
478
+ if not self.data_backend:
479
+ raise ValueError("Data Backend not specied for this source")
480
+
481
+ # Import the data backend module and extract its class_name
482
+ res = []
483
+ # Get the expected date at which this source should return its data (e.g. T-1)
484
+
485
+ # Loop over the backend returned generator
486
+ self.init_backend(execution_time)
487
+ errors = []
488
+ queryset = kwargs.pop("queryset", self.instantiated_backend.get_default_queryset())
489
+ if self.data_backend.provider and queryset is not None:
490
+ obj_external_ids = []
491
+ for obj in tqdm(queryset, total=queryset.count()):
492
+ try:
493
+ if external_id := self.get_or_create_provider_id(obj):
494
+ obj_external_ids.append(external_id)
495
+ except IntegrityError:
496
+ errors.append(str(obj))
497
+ kwargs["obj_external_ids"] = obj_external_ids
498
+ else:
499
+ kwargs["queryset"] = queryset
500
+ streams = self.instantiated_backend.get_files(
501
+ execution_time,
502
+ **{
503
+ **self.import_parameters,
504
+ **kwargs,
505
+ },
506
+ )
507
+ if streams:
508
+ for file_name, _file in streams:
509
+ content_file = ContentFile(_file.getvalue())
510
+ content_file.name = file_name
511
+ parser_handlers = self.parser_handler.all()
512
+ if only_handler:
513
+ parser_handlers = parser_handlers.filter(handler__iexact=only_handler)
514
+ for parser_handler in parser_handlers:
515
+ with transaction.atomic():
516
+ import_source = ImportSource.objects.create(
517
+ file=content_file,
518
+ parser_handler=parser_handler,
519
+ save_data=self.import_parameters.get(
520
+ "save_data_in_import_source", self.data_backend.save_data_in_import_source
521
+ ),
522
+ source=self,
523
+ )
524
+ res.append(import_source)
525
+ return res
526
+
527
+ @classmethod
528
+ def load_sources_from_settings(cls, settings: list[tuple[list[tuple[str, str]], str, dict[str, Any]]]):
529
+ """
530
+ Utility classmethod to parser sources from the settings.
531
+
532
+ We assume data structure as follow
533
+
534
+ [
535
+ (
536
+ [("module1.HandlerModel1", "module1.io.parsers.provider1.handler1")],
537
+ "module1.io.backends.provider1.handler1.DataBackend",
538
+ {
539
+ "credentials": [credential_dict],
540
+ "crontab": "10 9,17,1 * * *",
541
+ },
542
+ ),
543
+ (
544
+ [("module2.HandlerModel2", "module2.io.parsers.provider2.handler2")],
545
+ "module2.io.backends.provider2.handler2.DataBackend",
546
+ )
547
+ ]
548
+ Args:
549
+ settings: The source settings to parse
550
+
551
+ """
552
+ for parser_handlers, data_backend_module, config in settings:
553
+ with suppress(Exception):
554
+ # This method is loaded when server boots. If this fail, it can prevent the server from starting.
555
+ backend_class_path, _, backend_class_name = data_backend_module.rpartition(".")
556
+
557
+ data_backend = DataBackend.objects.get(
558
+ backend_class_path=backend_class_path, backend_class_name=backend_class_name
559
+ )
560
+ crontab = None
561
+ credentials = config.pop("credentials", [])
562
+ if crontab_data := config.pop("crontab", None):
563
+ minute, hour, day_of_month, month_of_year, day_of_week = crontab_data.split(" ")
564
+ crontab_kwargs = {
565
+ "minute": minute,
566
+ "hour": hour,
567
+ "day_of_week": day_of_week,
568
+ "day_of_month": day_of_month,
569
+ "month_of_year": month_of_year,
570
+ }
571
+ crontabs = CrontabSchedule.objects.filter(**crontab_kwargs)
572
+ if crontabs.exists():
573
+ crontab = crontabs.first()
574
+ else:
575
+ crontab = CrontabSchedule.objects.create(**crontab_kwargs)
576
+
577
+ source, source_created = Source.objects.get_or_create(
578
+ data_backend=data_backend,
579
+ defaults={"crontab": crontab, **config},
580
+ )
581
+ for handler, parser in parser_handlers:
582
+ parser_handler = ParserHandler.objects.get_or_create(parser=parser, handler=handler)[0]
583
+ if parser_handler not in source.parser_handler.all():
584
+ source.parser_handler.add(parser_handler)
585
+ for credential_data in credentials:
586
+ credential, created = ImportCredential.objects.get_or_create(
587
+ key=credential_data["key"], defaults=credential_data
588
+ )
589
+ if credential not in source.credentials.all():
590
+ source.credentials.add(credential)
591
+ source.save()
592
+
593
+ @classmethod
594
+ def get_representation_value_key(cls) -> str:
595
+ return "id"
596
+
597
+ @classmethod
598
+ def get_representation_label_key(cls) -> str:
599
+ return "{{title}} ({{id}})"
600
+
601
+ @classmethod
602
+ def get_representation_endpoint(cls) -> str:
603
+ return "wbcore:io:sourcerepresentation-list"
604
+
605
+
606
+ @receiver(post_delete, sender="io.Source")
607
+ def post_delete_source(sender, instance, **kwargs):
608
+ # Delete the attached period task
609
+ if instance.periodic_task:
610
+ instance.periodic_task.delete()
611
+
612
+
613
+ @receiver(pre_delete, sender="io.Source")
614
+ def pre_delete_source(sender, instance, **kwargs):
615
+ # Unassign import source from the deleting source
616
+ ImportSource.objects.filter(source=instance).update(source=None)
617
+
618
+
619
+ @shared_task(queue=Queue.BACKGROUND.value)
620
+ def trigger_workflow_as_task(source_id: int, execution_time: datetime | None = None, **kwargs):
621
+ """
622
+ Call the `import_source` as a celery task
623
+ """
624
+ source = Source.objects.get(id=source_id)
625
+ source.trigger_workflow(execution_time=execution_time, **kwargs)
626
+
627
+
628
+ @shared_task(queue=Queue.BACKGROUND.value)
629
+ def generate_import_sources_as_task(source_id: int, execution_time: datetime, **kwargs) -> list[int]:
630
+ """
631
+ Call the `import_source` as a celery task
632
+ """
633
+ source = Source.objects.get(id=source_id)
634
+ import_source_ids = [
635
+ import_source.pk for import_source in source.generate_import_sources(execution_time, **kwargs)
636
+ ] # convert to list to be json serializable
637
+ return import_source_ids
638
+
639
+
640
+ @shared_task(queue=Queue.BACKGROUND.value)
641
+ def process_import_sources_as_task(import_source_ids: list[int]):
642
+ """
643
+ Call the `import_source` as a celery task
644
+ """
645
+ for import_source_id in import_source_ids:
646
+ import_source = ImportSource.objects.get(id=import_source_id)
647
+ import_source.import_data(force_reimport=True)
648
+
649
+
650
+ class ImportExportSource(models.Model):
651
+ class Status(models.TextChoices):
652
+ PENDING = "PENDING", "Pending"
653
+ PROCESSED = "PROCESSED", "Processed"
654
+ WARNING = "WARNING", "Warning"
655
+ ERROR = "ERROR", "Error"
656
+ IGNORED = "IGNORED", "Ignore"
657
+
658
+ created = models.DateTimeField(auto_now_add=True)
659
+ last_updated = models.DateTimeField(auto_now=True)
660
+
661
+ status = models.CharField(
662
+ max_length=16,
663
+ choices=Status.choices,
664
+ default=Status.PENDING.value,
665
+ )
666
+
667
+ log = models.TextField(null=True, blank=True)
668
+ origin = models.CharField(max_length=255, null=True, blank=True)
669
+ creator = models.ForeignKey(
670
+ "authentication.User", null=True, blank=True, verbose_name="Creator", on_delete=models.SET_NULL
671
+ )
672
+ data = models.JSONField(default=dict, null=True, blank=True)
673
+ resource_kwargs = models.JSONField(default=dict, blank=True, null=False)
674
+
675
+ class Meta:
676
+ abstract = True
677
+
678
+
679
+ class ExportSource(ImportExportSource):
680
+ file = models.FileField(max_length=256, upload_to="io/export_source/files", null=True, blank=True)
681
+ format = models.IntegerField(
682
+ choices=ExportFormat.choices,
683
+ default=ExportFormat.CSV.value,
684
+ verbose_name=_("Format of file to be exported"),
685
+ )
686
+
687
+ content_type = models.ForeignKey(ContentType, verbose_name=_("Export job Content Type"), on_delete=models.CASCADE)
688
+
689
+ resource_path = models.CharField(
690
+ verbose_name=_("Resource path to use when exporting"), max_length=255, default="", blank=True, null=True
691
+ )
692
+
693
+ query_str = models.TextField(verbose_name=_("SQL query to be executed"), blank=True, null=False)
694
+ query_params = PickledObjectField( # we have to use picklefield because the sql parameters are given as python object. However, no django model should be stored in this field, which should be the case as these objects are given through their IDs
695
+ blank=True, verbose_name=_("SQL query parameters to be used with the sql query"), default=list
696
+ )
697
+
698
+ class Meta:
699
+ verbose_name = _("Export Source")
700
+ verbose_name_plural = _("Export Sources")
701
+ notification_types = [
702
+ create_notification_type(
703
+ code="io.export_done",
704
+ title="File Export has finished",
705
+ help_text="Notifies user when their submitted export files is done and available",
706
+ web=True,
707
+ mobile=True,
708
+ email=True,
709
+ resource_button_label="Download File",
710
+ )
711
+ ]
712
+ constraints = [
713
+ models.CheckConstraint(
714
+ condition=Q(query_str__isnull=False, resource_path__isnull=False) | ~Q(data__exact=dict()),
715
+ name="check_either_data_or_resource_isnotnull",
716
+ )
717
+ ]
718
+
719
+ def __str__(self) -> str:
720
+ return str(self.id)
721
+
722
+ @property
723
+ def file_format(self):
724
+ return get_django_import_export_format(self.format)()
725
+
726
+ @property
727
+ def resource(self) -> Resource:
728
+ """
729
+ Load into an attribute the instantiated resource loaded from the resource path
730
+ """
731
+ resource_class = import_from_dotted_path(self.resource_path)
732
+ return resource_class(**self.resource_kwargs)
733
+
734
+ @property
735
+ def queryset(self) -> models.QuerySet:
736
+ """
737
+ Recreate the base queryset from the content type model class base manager and the saved query string and parameters
738
+ """
739
+
740
+ return self.content_type.model_class().objects.raw(self.query_str, self.query_params)
741
+
742
+ def get_export_filename(self):
743
+ date_str = timezone.now().strftime("%Y-%m-%d")
744
+ ts = datetime.timestamp(timezone.now())
745
+ filename = "%s-%s_%s.%s" % (
746
+ self.content_type.model_class().__name__,
747
+ date_str,
748
+ ts,
749
+ self.file_format.get_extension(),
750
+ )
751
+ return filename
752
+
753
+ def notify(self):
754
+ """
755
+ Notify the user if the export job is done and accessible
756
+ """
757
+ if self.file and self.status == self.Status.PROCESSED:
758
+ send_notification(
759
+ code="io.export_done",
760
+ title=_("Your export file is available"),
761
+ body=_("<p>The export job you requested is finished and available for one hour.</p>").format(
762
+ url=self.file.url
763
+ ),
764
+ endpoint=self.file.url,
765
+ user=self.creator,
766
+ )
767
+
768
+ def export_data(self, debug: bool = False, **kwargs):
769
+ self.log = ""
770
+ self.status = self.Status.PENDING.name
771
+ self.save()
772
+ try:
773
+ file_format = get_django_import_export_format(self.format)()
774
+ if self.data:
775
+ # Data was saved as a dictionary from a tablib.dataset, we need to recreate it
776
+ data = Dataset()
777
+ data.headers = self.data["headers"]
778
+ data.extend(self.data["data"])
779
+ else:
780
+ data = self.resource.export(queryset=self.queryset)
781
+
782
+ export_data = file_format.export_data(data)
783
+ if not file_format.is_binary():
784
+ export_data = export_data.encode("utf-8")
785
+ self.file.save(self.get_export_filename(), ContentFile(export_data))
786
+ self.status = self.Status.PROCESSED.name
787
+ self.save()
788
+ self.notify()
789
+
790
+ except Exception as e:
791
+ if debug:
792
+ raise e
793
+ else:
794
+ ex_type, ex_value, _ = sys.exc_info()
795
+ self.status = self.Status.ERROR.name
796
+ self.log = f"{ex_type}: {ex_value}\n"
797
+ self.log += traceback.format_exc()
798
+ self.save()
799
+ logger.error(f"Export source {self.id} error: {e}")
800
+
801
+
802
+ class ImportSource(ImportExportSource):
803
+ file = models.FileField(max_length=256, upload_to="io/import_source/files", null=True, blank=True)
804
+
805
+ save_data = models.BooleanField(default=True, help_text="If True, will save the raw data in a field")
806
+ progress_index = models.PositiveIntegerField(default=0)
807
+
808
+ parser_handler = models.ForeignKey("io.ParserHandler", on_delete=models.CASCADE)
809
+ source = models.ForeignKey("io.Source", null=True, blank=True, verbose_name="Source", on_delete=models.SET_NULL)
810
+ errors_log = models.TextField(null=True, blank=True)
811
+
812
+ class Meta:
813
+ verbose_name = _("Import Source")
814
+ verbose_name_plural = _("Import Sources")
815
+ notification_types = [
816
+ create_notification_type(
817
+ code="io.import_done",
818
+ title="File Import has finished",
819
+ help_text="Notifies user when their submitted import files has finished",
820
+ )
821
+ ]
822
+
823
+ def __str__(self) -> str:
824
+ return f"{self.pk}"
825
+
826
+ def _validate_file_type(self):
827
+ """
828
+ Valid the file type based on the allowed types specified by the parser
829
+ Returns:
830
+ True if the filetype is valid
831
+ """
832
+ if (upload := self.file) and (allow_file_type := self.parser_handler.allow_file_type):
833
+ file_type = magic.from_buffer(upload.file.read(1024), mime=True)
834
+ if file_type != allow_file_type:
835
+ raise ValidationError("File type not supported.")
836
+
837
+ def save(self, *args, **kwargs):
838
+ # Check here company name, modify ...
839
+ self._validate_file_type()
840
+ super().save(*args, **kwargs)
841
+
842
+ def _parse_data(self) -> dict[str, Any]:
843
+ """
844
+ Given the ParserHandler linked object, we import its related module and call its defined `parse` function
845
+ Returns:
846
+ Dict The parsed data as a python dictionary
847
+ """
848
+ if not self.file:
849
+ raise ValueError("This import source does not include a valid file")
850
+ parsed_data = self.parser_handler.parse(self)
851
+ if self.save_data:
852
+ self.data = parsed_data
853
+ self.save()
854
+ return parsed_data
855
+
856
+ def _process_data(self, parsed_data: dict[str, Any], **kwargs):
857
+ """
858
+ Given the parsed data (as a python dictionary), import the corresponding handler and calls its defined `process
859
+ Args:
860
+ data: The deserialized data to be passed down to the parser
861
+ """
862
+ self.status = self.Status.PROCESSED.name
863
+ if parsed_data:
864
+ parsed_data_copy = deepcopy(parsed_data)
865
+ if data := parsed_data_copy.pop("data", None):
866
+ self.parser_handler.handle(self, {"data": data[self.progress_index :], **parsed_data_copy}, **kwargs)
867
+ if self.errors_log:
868
+ self.status = self.Status.WARNING.name
869
+ # clean log if we don't want to use space to save it
870
+ if not self.save_data:
871
+ self.log = ""
872
+ self.save()
873
+
874
+ def import_data(self, force_reimport: Optional[bool] = False, **kwargs):
875
+ """
876
+ General workflow method:
877
+ * Parse the data given the linked import file
878
+ * Handle the parsed data
879
+ * Change the import_source status based on sucess
880
+ Args:
881
+ silent: False if this method is not suppose to silently catch the exceptions
882
+ """
883
+ if force_reimport:
884
+ self.progress_index = 0
885
+ self.log = ""
886
+ self.errors_log = ""
887
+ # self.data = {}
888
+ self.status = self.Status.PENDING.name
889
+ self.save()
890
+ debug = kwargs.pop("debug", settings.DEBUG)
891
+ try:
892
+ data = self._parse_data()
893
+ self._process_data(data, debug=debug, **kwargs)
894
+ post_import.send(sender=self.parser_handler.model, import_source=self)
895
+
896
+ except Exception as e:
897
+ ex_type, ex_value, _ = sys.exc_info()
898
+ self.status = self.Status.ERROR.name
899
+ self.log = f"{ex_type}: {ex_value}\n"
900
+ self.log += traceback.format_exc()
901
+ self.save()
902
+ if debug:
903
+ raise e
904
+ elif not self.creator: # if a creator is set in this import source, they will receive a proper notification with feedback. No need then to pollute the logger
905
+ logger.error(f"Could not import file {self.file.name} (parser/handler: {self.parser_handler})")
906
+
907
+ self.notify()
908
+
909
+ def notify(self):
910
+ if self.creator:
911
+ body = f"""<p><strong>File Name Processed:</strong> {self.file.name}</p>
912
+ <p><strong>Number of Rows:</strong> {self.progress_index}</p>
913
+ <p><strong>Status:</strong> {self.Status[self.status].label}</p>"""
914
+ if self.status == self.Status.ERROR:
915
+ body += "<p>While processing the import, we encountered an unrecoverable error. Please contact a system administrator.</p>"
916
+ elif self.status == self.Status.WARNING and self.errors_log:
917
+ body += f"""<p><strong>Warning:</strong> Some rows were ignored during import.</p>
918
+ <p><strong>Ignored Rows:</strong></p>
919
+ <ul>{"".join(["<li>" + line + "</li>" for line in io.StringIO(self.errors_log)])}</ul>"""
920
+ send_notification(
921
+ code="io.import_done",
922
+ title=f"Your import finished with status {self.Status[self.status].label}",
923
+ body=body,
924
+ user=self.creator,
925
+ )
926
+
927
+ @classmethod
928
+ def get_representation_value_key(cls) -> str:
929
+ return "id"
930
+
931
+ @classmethod
932
+ def get_representation_label_key(cls) -> str:
933
+ return "{{file}}"
934
+
935
+ @classmethod
936
+ def get_representation_endpoint(cls) -> str:
937
+ return "wbcore:io:importsourcerepresentation-list"
938
+
939
+
940
+ @shared_task(queue=Queue.BACKGROUND.value)
941
+ def import_data_as_task(import_source_id: int, **kwargs):
942
+ """
943
+ Call `import_data` as a celery task
944
+ :param Int The import_source id
945
+ """
946
+ import_source = ImportSource.objects.get(id=import_source_id)
947
+ import_source.import_data(**kwargs)
948
+
949
+
950
+ @shared_task(queue=Queue.DEFAULT.value)
951
+ def export_data_as_task(export_source_id: int, **kwargs):
952
+ """
953
+ Call `import_data` as a celery task
954
+ :param Int The import_source id
955
+ """
956
+ export_source = ExportSource.objects.get(id=export_source_id)
957
+ export_source.export_data(**kwargs)
958
+
959
+
960
+ @receiver(models.signals.post_save, sender=ExportSource)
961
+ def post_save_export_source(sender, instance: ExportSource, created: bool, raw: bool, **kwargs):
962
+ """Triggers the export task on creation"""
963
+
964
+ if not raw and created and instance.status == ExportSource.Status.PENDING:
965
+ transaction.on_commit(lambda: export_data_as_task.delay(instance.id))
966
+
967
+
968
+ def validate_key_file(value: models.FileField):
969
+ ext = os.path.splitext(value.name)[1]
970
+ if ext.lower() != ".key":
971
+ raise ValidationError("The file extension needs to be a .key")
972
+
973
+
974
+ def validate_pem_file(value: models.FileField):
975
+ ext = os.path.splitext(value.name)[1]
976
+ if ext.lower() != ".pem":
977
+ raise ValidationError("The file extension needs to be a .pem")
978
+
979
+
980
+ class ImportCredential(models.Model):
981
+ class Type(models.TextChoices):
982
+ CREDENTIAL = "CREDENTIAL", "Credential"
983
+ AUTHENTICATION_TOKEN = "AUTHENTICATION_TOKEN", "Authentication Token"
984
+ CERTIFICATE = "CERTIFICATE", "Certificate"
985
+
986
+ key = models.CharField(max_length=255)
987
+ type = models.CharField(choices=Type.choices, default=Type.CREDENTIAL, max_length=64)
988
+
989
+ username = models.CharField(max_length=255, null=True, blank=True)
990
+ password = models.CharField(max_length=255, null=True, blank=True)
991
+
992
+ authentication_token = models.CharField(max_length=2048, null=True, blank=True)
993
+
994
+ certificate_pem = models.FileField(
995
+ max_length=256,
996
+ null=True,
997
+ blank=True,
998
+ upload_to="io/import_credential/certificates",
999
+ help_text="We are expecting a .cert file",
1000
+ validators=[validate_pem_file],
1001
+ )
1002
+ certificate_key = models.FileField(
1003
+ max_length=256,
1004
+ null=True,
1005
+ blank=True,
1006
+ upload_to="io/import_credential/certificates",
1007
+ help_text="We are expecting a .key file",
1008
+ validators=[validate_key_file],
1009
+ )
1010
+
1011
+ additional_resources = models.JSONField(default=dict, null=True, blank=True)
1012
+
1013
+ validity_start = models.DateTimeField(null=True, blank=True)
1014
+ validity_end = models.DateTimeField(null=True, blank=True)
1015
+
1016
+ def save(self, *args, **kwargs):
1017
+ if self.type == self.Type.CREDENTIAL and (not self.username or not self.password):
1018
+ raise ValidationError("The type is credential, you need to specify a username and a password")
1019
+ elif self.type == self.Type.AUTHENTICATION_TOKEN and not self.authentication_token:
1020
+ raise ValidationError("The type is authentication header, you need to specify a valid header")
1021
+ elif self.type == self.Type.CERTIFICATE and (not self.certificate_key or not self.certificate_pem):
1022
+ raise ValidationError("The type is certificate, you need to specify valid public and private key files")
1023
+ super().save(*args, **kwargs)
1024
+
1025
+ class Meta:
1026
+ verbose_name = _("Import Credential")
1027
+ verbose_name_plural = _("Import Credentials")
1028
+
1029
+ def __str__(self) -> str:
1030
+ dates_repr = ""
1031
+ if self.validity_start:
1032
+ dates_repr += f"{self.validity_start:%Y-%m-%d %H:%M:%S}"
1033
+ if self.validity_end:
1034
+ dates_repr += f"- {self.validity_end:%Y-%m-%d %H:%M:%S}"
1035
+ if dates_repr:
1036
+ return f"{self.key} ({dates_repr})"
1037
+ return self.key