trueppm-api 0.4.0b2__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 (759) hide show
  1. trueppm_api/__init__.py +14 -0
  2. trueppm_api/apps/access/__init__.py +0 -0
  3. trueppm_api/apps/access/apps.py +19 -0
  4. trueppm_api/apps/access/groups.py +518 -0
  5. trueppm_api/apps/access/management/__init__.py +0 -0
  6. trueppm_api/apps/access/management/commands/__init__.py +0 -0
  7. trueppm_api/apps/access/management/commands/create_admin.py +164 -0
  8. trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
  9. trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
  10. trueppm_api/apps/access/migrations/0001_initial.py +61 -0
  11. trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
  12. trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
  13. trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
  14. trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
  15. trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
  16. trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
  17. trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
  18. trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
  19. trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
  20. trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
  21. trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
  22. trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
  23. trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
  24. trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
  25. trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
  26. trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
  27. trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
  28. trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
  29. trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
  30. trueppm_api/apps/access/migrations/__init__.py +0 -0
  31. trueppm_api/apps/access/models.py +588 -0
  32. trueppm_api/apps/access/permissions.py +2837 -0
  33. trueppm_api/apps/access/serializers.py +946 -0
  34. trueppm_api/apps/access/services.py +667 -0
  35. trueppm_api/apps/access/signals.py +103 -0
  36. trueppm_api/apps/access/tasks.py +50 -0
  37. trueppm_api/apps/access/throttles.py +95 -0
  38. trueppm_api/apps/access/urls.py +191 -0
  39. trueppm_api/apps/access/views.py +1931 -0
  40. trueppm_api/apps/agents/__init__.py +6 -0
  41. trueppm_api/apps/agents/apps.py +11 -0
  42. trueppm_api/apps/agents/canonical.py +73 -0
  43. trueppm_api/apps/agents/deferred.py +108 -0
  44. trueppm_api/apps/agents/management/__init__.py +0 -0
  45. trueppm_api/apps/agents/management/commands/__init__.py +0 -0
  46. trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
  47. trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
  48. trueppm_api/apps/agents/middleware.py +44 -0
  49. trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
  50. trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
  51. trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
  52. trueppm_api/apps/agents/migrations/__init__.py +0 -0
  53. trueppm_api/apps/agents/models.py +371 -0
  54. trueppm_api/apps/agents/refusal.py +103 -0
  55. trueppm_api/apps/agents/serializers.py +85 -0
  56. trueppm_api/apps/agents/services.py +397 -0
  57. trueppm_api/apps/agents/signals.py +30 -0
  58. trueppm_api/apps/agents/urls.py +15 -0
  59. trueppm_api/apps/agents/views.py +108 -0
  60. trueppm_api/apps/csvimport/__init__.py +12 -0
  61. trueppm_api/apps/csvimport/apps.py +11 -0
  62. trueppm_api/apps/csvimport/mapping.py +363 -0
  63. trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
  64. trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
  65. trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
  66. trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
  67. trueppm_api/apps/csvimport/models.py +111 -0
  68. trueppm_api/apps/csvimport/parser.py +1825 -0
  69. trueppm_api/apps/csvimport/services.py +48 -0
  70. trueppm_api/apps/csvimport/tasks.py +418 -0
  71. trueppm_api/apps/csvimport/template.py +37 -0
  72. trueppm_api/apps/csvimport/urls.py +50 -0
  73. trueppm_api/apps/csvimport/views.py +793 -0
  74. trueppm_api/apps/history/__init__.py +0 -0
  75. trueppm_api/apps/history/apps.py +12 -0
  76. trueppm_api/apps/history/changelog.py +359 -0
  77. trueppm_api/apps/history/diff_policy.py +177 -0
  78. trueppm_api/apps/history/serializers.py +104 -0
  79. trueppm_api/apps/history/signals.py +82 -0
  80. trueppm_api/apps/history/tasks.py +105 -0
  81. trueppm_api/apps/history/urls.py +38 -0
  82. trueppm_api/apps/history/views.py +443 -0
  83. trueppm_api/apps/idempotency/__init__.py +0 -0
  84. trueppm_api/apps/idempotency/apps.py +11 -0
  85. trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
  86. trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
  87. trueppm_api/apps/idempotency/mixins.py +226 -0
  88. trueppm_api/apps/idempotency/models.py +75 -0
  89. trueppm_api/apps/idempotency/tasks.py +61 -0
  90. trueppm_api/apps/integrations/__init__.py +13 -0
  91. trueppm_api/apps/integrations/apps.py +59 -0
  92. trueppm_api/apps/integrations/connections.py +806 -0
  93. trueppm_api/apps/integrations/encryption.py +92 -0
  94. trueppm_api/apps/integrations/external_sources.py +837 -0
  95. trueppm_api/apps/integrations/git_automation_services.py +231 -0
  96. trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
  97. trueppm_api/apps/integrations/http.py +403 -0
  98. trueppm_api/apps/integrations/me_work.py +218 -0
  99. trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
  100. trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
  101. trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
  102. trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
  103. trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
  104. trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
  105. trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
  106. trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
  107. trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
  108. trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
  109. trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
  110. trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
  111. trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
  112. trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
  113. trueppm_api/apps/integrations/migrations/__init__.py +0 -0
  114. trueppm_api/apps/integrations/models.py +622 -0
  115. trueppm_api/apps/integrations/notification_channels.py +57 -0
  116. trueppm_api/apps/integrations/opengraph.py +229 -0
  117. trueppm_api/apps/integrations/outgoing.py +201 -0
  118. trueppm_api/apps/integrations/providers.py +519 -0
  119. trueppm_api/apps/integrations/registry.py +345 -0
  120. trueppm_api/apps/integrations/serializers.py +453 -0
  121. trueppm_api/apps/integrations/services.py +171 -0
  122. trueppm_api/apps/integrations/tasks.py +731 -0
  123. trueppm_api/apps/integrations/throttles.py +165 -0
  124. trueppm_api/apps/integrations/urls.py +77 -0
  125. trueppm_api/apps/integrations/views.py +1106 -0
  126. trueppm_api/apps/jiraimport/__init__.py +8 -0
  127. trueppm_api/apps/jiraimport/apps.py +11 -0
  128. trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
  129. trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
  130. trueppm_api/apps/jiraimport/models.py +70 -0
  131. trueppm_api/apps/jiraimport/parser.py +350 -0
  132. trueppm_api/apps/jiraimport/services.py +46 -0
  133. trueppm_api/apps/jiraimport/tasks.py +277 -0
  134. trueppm_api/apps/jiraimport/urls.py +15 -0
  135. trueppm_api/apps/jiraimport/views.py +153 -0
  136. trueppm_api/apps/msproject/__init__.py +0 -0
  137. trueppm_api/apps/msproject/apps.py +11 -0
  138. trueppm_api/apps/msproject/dataclasses.py +153 -0
  139. trueppm_api/apps/msproject/exporter.py +500 -0
  140. trueppm_api/apps/msproject/extended_attributes.py +77 -0
  141. trueppm_api/apps/msproject/importer.py +1095 -0
  142. trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
  143. trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
  144. trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
  145. trueppm_api/apps/msproject/migrations/__init__.py +0 -0
  146. trueppm_api/apps/msproject/models.py +81 -0
  147. trueppm_api/apps/msproject/parser.py +1231 -0
  148. trueppm_api/apps/msproject/serializers.py +136 -0
  149. trueppm_api/apps/msproject/services.py +58 -0
  150. trueppm_api/apps/msproject/tasks.py +436 -0
  151. trueppm_api/apps/msproject/urls.py +39 -0
  152. trueppm_api/apps/msproject/views.py +531 -0
  153. trueppm_api/apps/notifications/__init__.py +7 -0
  154. trueppm_api/apps/notifications/apps.py +15 -0
  155. trueppm_api/apps/notifications/backfill.py +79 -0
  156. trueppm_api/apps/notifications/categories.py +112 -0
  157. trueppm_api/apps/notifications/delivery_limits.py +258 -0
  158. trueppm_api/apps/notifications/digests.py +447 -0
  159. trueppm_api/apps/notifications/email_backend.py +330 -0
  160. trueppm_api/apps/notifications/email_health.py +128 -0
  161. trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
  162. trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
  163. trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
  164. trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
  165. trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
  166. trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
  167. trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
  168. trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
  169. trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
  170. trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
  171. trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
  172. trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
  173. trueppm_api/apps/notifications/migrations/__init__.py +0 -0
  174. trueppm_api/apps/notifications/models.py +1197 -0
  175. trueppm_api/apps/notifications/receivers.py +155 -0
  176. trueppm_api/apps/notifications/schema_migrations.py +56 -0
  177. trueppm_api/apps/notifications/serializers.py +961 -0
  178. trueppm_api/apps/notifications/services.py +1451 -0
  179. trueppm_api/apps/notifications/tasks.py +711 -0
  180. trueppm_api/apps/notifications/throttles.py +98 -0
  181. trueppm_api/apps/notifications/urls.py +97 -0
  182. trueppm_api/apps/notifications/views.py +904 -0
  183. trueppm_api/apps/observability/__init__.py +0 -0
  184. trueppm_api/apps/observability/apps.py +31 -0
  185. trueppm_api/apps/observability/logging.py +213 -0
  186. trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
  187. trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
  188. trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
  189. trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
  190. trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
  191. trueppm_api/apps/observability/migrations/__init__.py +0 -0
  192. trueppm_api/apps/observability/models.py +141 -0
  193. trueppm_api/apps/observability/otel/__init__.py +45 -0
  194. trueppm_api/apps/observability/otel/attributes.py +247 -0
  195. trueppm_api/apps/observability/otel/export_health.py +533 -0
  196. trueppm_api/apps/observability/otel/instrumentation.py +351 -0
  197. trueppm_api/apps/observability/otel/metrics.py +551 -0
  198. trueppm_api/apps/observability/otel/provider.py +511 -0
  199. trueppm_api/apps/observability/otel/request_attributes.py +204 -0
  200. trueppm_api/apps/observability/purge_registry.py +124 -0
  201. trueppm_api/apps/observability/retention.py +139 -0
  202. trueppm_api/apps/observability/selectors.py +969 -0
  203. trueppm_api/apps/observability/serializers.py +142 -0
  204. trueppm_api/apps/observability/services.py +407 -0
  205. trueppm_api/apps/observability/tasks.py +266 -0
  206. trueppm_api/apps/observability/urls.py +27 -0
  207. trueppm_api/apps/observability/views.py +523 -0
  208. trueppm_api/apps/profiles/__init__.py +0 -0
  209. trueppm_api/apps/profiles/apps.py +15 -0
  210. trueppm_api/apps/profiles/constants.py +54 -0
  211. trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
  212. trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
  213. trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
  214. trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
  215. trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
  216. trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
  217. trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
  218. trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
  219. trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
  220. trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
  221. trueppm_api/apps/profiles/migrations/__init__.py +0 -0
  222. trueppm_api/apps/profiles/models.py +276 -0
  223. trueppm_api/apps/profiles/serializers.py +199 -0
  224. trueppm_api/apps/profiles/services.py +466 -0
  225. trueppm_api/apps/profiles/views.py +124 -0
  226. trueppm_api/apps/projects/__init__.py +0 -0
  227. trueppm_api/apps/projects/actual_date_rules.py +163 -0
  228. trueppm_api/apps/projects/amend.py +133 -0
  229. trueppm_api/apps/projects/apps.py +38 -0
  230. trueppm_api/apps/projects/asset_feed.py +459 -0
  231. trueppm_api/apps/projects/asset_views.py +317 -0
  232. trueppm_api/apps/projects/attachment_policy.py +278 -0
  233. trueppm_api/apps/projects/authentication.py +449 -0
  234. trueppm_api/apps/projects/backfill.py +287 -0
  235. trueppm_api/apps/projects/backlog_services.py +324 -0
  236. trueppm_api/apps/projects/backlog_views.py +302 -0
  237. trueppm_api/apps/projects/batch_operation_views.py +264 -0
  238. trueppm_api/apps/projects/blocker_services.py +510 -0
  239. trueppm_api/apps/projects/board_activity.py +462 -0
  240. trueppm_api/apps/projects/board_activity_views.py +186 -0
  241. trueppm_api/apps/projects/board_lanes.py +134 -0
  242. trueppm_api/apps/projects/bulk_settings.py +194 -0
  243. trueppm_api/apps/projects/bundled_templates.py +215 -0
  244. trueppm_api/apps/projects/calendar_settings.py +180 -0
  245. trueppm_api/apps/projects/ceremony_views.py +244 -0
  246. trueppm_api/apps/projects/commit_moment.py +204 -0
  247. trueppm_api/apps/projects/config_notice.py +1325 -0
  248. trueppm_api/apps/projects/custom_field_values.py +266 -0
  249. trueppm_api/apps/projects/decisions_services.py +93 -0
  250. trueppm_api/apps/projects/decisions_views.py +94 -0
  251. trueppm_api/apps/projects/estimation_scale.py +126 -0
  252. trueppm_api/apps/projects/export_bundle.py +353 -0
  253. trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
  254. trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
  255. trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
  256. trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
  257. trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
  258. trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
  259. trueppm_api/apps/projects/inbound_sync.py +555 -0
  260. trueppm_api/apps/projects/iteration_label.py +85 -0
  261. trueppm_api/apps/projects/lifecycle.py +191 -0
  262. trueppm_api/apps/projects/management/__init__.py +0 -0
  263. trueppm_api/apps/projects/management/commands/__init__.py +0 -0
  264. trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
  265. trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
  266. trueppm_api/apps/projects/management/commands/export_program.py +50 -0
  267. trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
  268. trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
  269. trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
  270. trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
  271. trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
  272. trueppm_api/apps/projects/mcp_settings.py +305 -0
  273. trueppm_api/apps/projects/methodology.py +178 -0
  274. trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
  275. trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
  276. trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
  277. trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
  278. trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
  279. trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
  280. trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
  281. trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
  282. trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
  283. trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
  284. trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
  285. trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
  286. trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
  287. trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
  288. trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
  289. trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
  290. trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
  291. trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
  292. trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
  293. trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
  294. trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
  295. trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
  296. trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
  297. trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
  298. trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
  299. trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
  300. trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
  301. trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
  302. trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
  303. trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
  304. trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
  305. trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
  306. trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
  307. trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
  308. trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
  309. trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
  310. trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
  311. trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
  312. trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
  313. trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
  314. trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
  315. trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
  316. trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
  317. trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
  318. trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
  319. trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
  320. trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
  321. trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
  322. trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
  323. trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
  324. trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
  325. trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
  326. trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
  327. trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
  328. trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
  329. trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
  330. trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
  331. trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
  332. trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
  333. trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
  334. trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
  335. trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
  336. trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
  337. trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
  338. trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
  339. trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
  340. trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
  341. trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
  342. trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
  343. trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
  344. trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
  345. trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
  346. trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
  347. trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
  348. trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
  349. trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
  350. trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
  351. trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
  352. trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
  353. trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
  354. trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
  355. trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
  356. trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
  357. trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
  358. trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
  359. trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
  360. trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
  361. trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
  362. trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
  363. trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
  364. trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
  365. trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
  366. trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
  367. trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
  368. trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
  369. trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
  370. trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
  371. trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
  372. trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
  373. trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
  374. trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
  375. trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
  376. trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
  377. trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
  378. trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
  379. trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
  380. trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
  381. trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
  382. trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
  383. trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
  384. trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
  385. trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
  386. trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
  387. trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
  388. trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
  389. trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
  390. trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
  391. trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
  392. trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
  393. trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
  394. trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
  395. trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
  396. trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
  397. trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
  398. trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
  399. trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
  400. trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
  401. trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
  402. trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
  403. trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
  404. trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
  405. trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
  406. trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
  407. trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
  408. trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
  409. trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
  410. trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
  411. trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
  412. trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
  413. trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
  414. trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
  415. trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
  416. trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
  417. trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
  418. trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
  419. trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
  420. trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
  421. trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
  422. trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
  423. trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
  424. trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
  425. trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
  426. trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
  427. trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
  428. trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
  429. trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
  430. trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
  431. trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
  432. trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
  433. trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
  434. trueppm_api/apps/projects/migrations/__init__.py +0 -0
  435. trueppm_api/apps/projects/models.py +8946 -0
  436. trueppm_api/apps/projects/poker_services.py +154 -0
  437. trueppm_api/apps/projects/poker_views.py +398 -0
  438. trueppm_api/apps/projects/product_backlog_services.py +450 -0
  439. trueppm_api/apps/projects/program_label_services.py +94 -0
  440. trueppm_api/apps/projects/program_label_views.py +189 -0
  441. trueppm_api/apps/projects/program_rollup.py +575 -0
  442. trueppm_api/apps/projects/program_schedule.py +745 -0
  443. trueppm_api/apps/projects/program_views.py +3220 -0
  444. trueppm_api/apps/projects/project_templates.py +422 -0
  445. trueppm_api/apps/projects/receivers.py +234 -0
  446. trueppm_api/apps/projects/refusal_codes.py +140 -0
  447. trueppm_api/apps/projects/reorder_services.py +145 -0
  448. trueppm_api/apps/projects/restructure_hooks.py +54 -0
  449. trueppm_api/apps/projects/retro_board_services.py +368 -0
  450. trueppm_api/apps/projects/retro_services.py +285 -0
  451. trueppm_api/apps/projects/risk_import.py +396 -0
  452. trueppm_api/apps/projects/row_identity.py +113 -0
  453. trueppm_api/apps/projects/schema_migrations.py +228 -0
  454. trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
  455. trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
  456. trueppm_api/apps/projects/seed/__init__.py +61 -0
  457. trueppm_api/apps/projects/seed/exporter.py +1644 -0
  458. trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
  459. trueppm_api/apps/projects/seed/importer.py +2390 -0
  460. trueppm_api/apps/projects/seed/reanchor.py +551 -0
  461. trueppm_api/apps/projects/seed/reldates.py +126 -0
  462. trueppm_api/apps/projects/seed/replace.py +126 -0
  463. trueppm_api/apps/projects/seed/replay.py +1691 -0
  464. trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
  465. trueppm_api/apps/projects/seed/samples.py +427 -0
  466. trueppm_api/apps/projects/seed/validation.py +1149 -0
  467. trueppm_api/apps/projects/serializers.py +12310 -0
  468. trueppm_api/apps/projects/services.py +6966 -0
  469. trueppm_api/apps/projects/share_serializers.py +132 -0
  470. trueppm_api/apps/projects/share_services.py +325 -0
  471. trueppm_api/apps/projects/share_views.py +374 -0
  472. trueppm_api/apps/projects/sharing_settings.py +187 -0
  473. trueppm_api/apps/projects/signal_privacy_services.py +909 -0
  474. trueppm_api/apps/projects/signal_privacy_views.py +476 -0
  475. trueppm_api/apps/projects/signals.py +132 -0
  476. trueppm_api/apps/projects/slip_conflict.py +266 -0
  477. trueppm_api/apps/projects/sprint_cadence.py +453 -0
  478. trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
  479. trueppm_api/apps/projects/standup.py +304 -0
  480. trueppm_api/apps/projects/standup_views.py +99 -0
  481. trueppm_api/apps/projects/structural_operation_services.py +727 -0
  482. trueppm_api/apps/projects/structural_operation_views.py +311 -0
  483. trueppm_api/apps/projects/surface_visibility.py +126 -0
  484. trueppm_api/apps/projects/task_batch_services.py +379 -0
  485. trueppm_api/apps/projects/task_bulk.py +1152 -0
  486. trueppm_api/apps/projects/task_classification.py +561 -0
  487. trueppm_api/apps/projects/task_classification_defaults.py +95 -0
  488. trueppm_api/apps/projects/task_duration_settings.py +142 -0
  489. trueppm_api/apps/projects/task_grouping.py +754 -0
  490. trueppm_api/apps/projects/tasks.py +1917 -0
  491. trueppm_api/apps/projects/template_divergence.py +168 -0
  492. trueppm_api/apps/projects/template_services.py +160 -0
  493. trueppm_api/apps/projects/template_tasks.py +258 -0
  494. trueppm_api/apps/projects/template_views.py +799 -0
  495. trueppm_api/apps/projects/throttles.py +311 -0
  496. trueppm_api/apps/projects/urls.py +1011 -0
  497. trueppm_api/apps/projects/utilization.py +885 -0
  498. trueppm_api/apps/projects/views.py +20572 -0
  499. trueppm_api/apps/projects/wbs_paths.py +119 -0
  500. trueppm_api/apps/resources/__init__.py +0 -0
  501. trueppm_api/apps/resources/apps.py +8 -0
  502. trueppm_api/apps/resources/capacity.py +121 -0
  503. trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
  504. trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
  505. trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
  506. trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
  507. trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
  508. trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
  509. trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
  510. trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
  511. trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
  512. trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
  513. trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
  514. trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
  515. trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
  516. trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
  517. trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
  518. trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
  519. trueppm_api/apps/resources/migrations/__init__.py +0 -0
  520. trueppm_api/apps/resources/models.py +298 -0
  521. trueppm_api/apps/resources/serializers.py +426 -0
  522. trueppm_api/apps/resources/services.py +465 -0
  523. trueppm_api/apps/resources/urls.py +28 -0
  524. trueppm_api/apps/resources/views.py +1749 -0
  525. trueppm_api/apps/scheduling/__init__.py +0 -0
  526. trueppm_api/apps/scheduling/apps.py +153 -0
  527. trueppm_api/apps/scheduling/calendars.py +119 -0
  528. trueppm_api/apps/scheduling/deadletter.py +91 -0
  529. trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
  530. trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
  531. trueppm_api/apps/scheduling/graph_guard.py +263 -0
  532. trueppm_api/apps/scheduling/management/__init__.py +0 -0
  533. trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
  534. trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
  535. trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
  536. trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
  537. trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
  538. trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
  539. trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
  540. trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
  541. trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
  542. trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
  543. trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
  544. trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
  545. trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
  546. trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
  547. trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
  548. trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
  549. trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
  550. trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
  551. trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
  552. trueppm_api/apps/scheduling/models.py +516 -0
  553. trueppm_api/apps/scheduling/receivers.py +160 -0
  554. trueppm_api/apps/scheduling/risk_premium.py +283 -0
  555. trueppm_api/apps/scheduling/serializers.py +564 -0
  556. trueppm_api/apps/scheduling/services.py +1182 -0
  557. trueppm_api/apps/scheduling/signals.py +56 -0
  558. trueppm_api/apps/scheduling/tasks.py +1984 -0
  559. trueppm_api/apps/scheduling/telemetry.py +80 -0
  560. trueppm_api/apps/scheduling/units.py +35 -0
  561. trueppm_api/apps/scheduling/urls.py +57 -0
  562. trueppm_api/apps/scheduling/views.py +2564 -0
  563. trueppm_api/apps/sso/__init__.py +0 -0
  564. trueppm_api/apps/sso/apps.py +19 -0
  565. trueppm_api/apps/sso/extensions.py +130 -0
  566. trueppm_api/apps/sso/management/__init__.py +0 -0
  567. trueppm_api/apps/sso/management/commands/__init__.py +0 -0
  568. trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
  569. trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
  570. trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
  571. trueppm_api/apps/sso/migrations/__init__.py +0 -0
  572. trueppm_api/apps/sso/models.py +148 -0
  573. trueppm_api/apps/sso/serializers.py +437 -0
  574. trueppm_api/apps/sso/services.py +1385 -0
  575. trueppm_api/apps/sso/urls.py +47 -0
  576. trueppm_api/apps/sso/views.py +935 -0
  577. trueppm_api/apps/sync/__init__.py +0 -0
  578. trueppm_api/apps/sync/apps.py +15 -0
  579. trueppm_api/apps/sync/backfill.py +208 -0
  580. trueppm_api/apps/sync/broadcast.py +320 -0
  581. trueppm_api/apps/sync/conflict.py +262 -0
  582. trueppm_api/apps/sync/consumers.py +471 -0
  583. trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
  584. trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
  585. trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
  586. trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
  587. trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
  588. trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
  589. trueppm_api/apps/sync/migrations/__init__.py +0 -0
  590. trueppm_api/apps/sync/models.py +225 -0
  591. trueppm_api/apps/sync/pagination.py +231 -0
  592. trueppm_api/apps/sync/sequence.py +371 -0
  593. trueppm_api/apps/sync/serializers.py +687 -0
  594. trueppm_api/apps/sync/tasks.py +260 -0
  595. trueppm_api/apps/sync/throttles.py +175 -0
  596. trueppm_api/apps/sync/upload.py +501 -0
  597. trueppm_api/apps/sync/urls.py +17 -0
  598. trueppm_api/apps/sync/views.py +930 -0
  599. trueppm_api/apps/sync/ws_auth.py +193 -0
  600. trueppm_api/apps/taskruns/__init__.py +0 -0
  601. trueppm_api/apps/taskruns/apps.py +11 -0
  602. trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
  603. trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
  604. trueppm_api/apps/taskruns/models.py +68 -0
  605. trueppm_api/apps/taskruns/serializers.py +92 -0
  606. trueppm_api/apps/taskruns/tasks.py +60 -0
  607. trueppm_api/apps/taskruns/tracker.py +286 -0
  608. trueppm_api/apps/taskruns/urls.py +46 -0
  609. trueppm_api/apps/taskruns/views.py +195 -0
  610. trueppm_api/apps/teams/__init__.py +8 -0
  611. trueppm_api/apps/teams/apps.py +16 -0
  612. trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
  613. trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
  614. trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
  615. trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
  616. trueppm_api/apps/teams/migrations/__init__.py +0 -0
  617. trueppm_api/apps/teams/models.py +145 -0
  618. trueppm_api/apps/teams/permissions.py +149 -0
  619. trueppm_api/apps/teams/serializers.py +95 -0
  620. trueppm_api/apps/teams/services.py +434 -0
  621. trueppm_api/apps/teams/signals.py +61 -0
  622. trueppm_api/apps/teams/urls.py +34 -0
  623. trueppm_api/apps/teams/views.py +201 -0
  624. trueppm_api/apps/timetracking/__init__.py +0 -0
  625. trueppm_api/apps/timetracking/apps.py +9 -0
  626. trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
  627. trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
  628. trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
  629. trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
  630. trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
  631. trueppm_api/apps/timetracking/models.py +178 -0
  632. trueppm_api/apps/timetracking/serializers.py +175 -0
  633. trueppm_api/apps/timetracking/services.py +117 -0
  634. trueppm_api/apps/timetracking/urls.py +47 -0
  635. trueppm_api/apps/timetracking/views.py +526 -0
  636. trueppm_api/apps/webhooks/__init__.py +0 -0
  637. trueppm_api/apps/webhooks/apps.py +8 -0
  638. trueppm_api/apps/webhooks/backfill.py +90 -0
  639. trueppm_api/apps/webhooks/dispatch.py +144 -0
  640. trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
  641. trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
  642. trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
  643. trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
  644. trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
  645. trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
  646. trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
  647. trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
  648. trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
  649. trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
  650. trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
  651. trueppm_api/apps/webhooks/models.py +372 -0
  652. trueppm_api/apps/webhooks/serializers.py +318 -0
  653. trueppm_api/apps/webhooks/tasks.py +663 -0
  654. trueppm_api/apps/webhooks/urls.py +22 -0
  655. trueppm_api/apps/webhooks/views.py +383 -0
  656. trueppm_api/apps/workflow_engine/__init__.py +0 -0
  657. trueppm_api/apps/workflow_engine/apps.py +23 -0
  658. trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
  659. trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
  660. trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
  661. trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
  662. trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
  663. trueppm_api/apps/workflow_engine/models.py +265 -0
  664. trueppm_api/apps/workflow_engine/tasks.py +368 -0
  665. trueppm_api/apps/workspace/__init__.py +0 -0
  666. trueppm_api/apps/workspace/apps.py +10 -0
  667. trueppm_api/apps/workspace/backfill.py +111 -0
  668. trueppm_api/apps/workspace/export.py +242 -0
  669. trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
  670. trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
  671. trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
  672. trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
  673. trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
  674. trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
  675. trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
  676. trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
  677. trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
  678. trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
  679. trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
  680. trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
  681. trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
  682. trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
  683. trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
  684. trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
  685. trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
  686. trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
  687. trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
  688. trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
  689. trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
  690. trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
  691. trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
  692. trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
  693. trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
  694. trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
  695. trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
  696. trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
  697. trueppm_api/apps/workspace/migrations/__init__.py +0 -0
  698. trueppm_api/apps/workspace/models.py +857 -0
  699. trueppm_api/apps/workspace/permissions.py +187 -0
  700. trueppm_api/apps/workspace/serializers.py +590 -0
  701. trueppm_api/apps/workspace/services.py +919 -0
  702. trueppm_api/apps/workspace/signals.py +37 -0
  703. trueppm_api/apps/workspace/tasks.py +555 -0
  704. trueppm_api/apps/workspace/urls.py +113 -0
  705. trueppm_api/apps/workspace/views.py +1502 -0
  706. trueppm_api/asgi.py +42 -0
  707. trueppm_api/celery.py +31 -0
  708. trueppm_api/core/__init__.py +0 -0
  709. trueppm_api/core/admin_site.py +206 -0
  710. trueppm_api/core/auth_views.py +916 -0
  711. trueppm_api/core/constant_time.py +51 -0
  712. trueppm_api/core/csp.py +54 -0
  713. trueppm_api/core/db.py +87 -0
  714. trueppm_api/core/db_session.py +37 -0
  715. trueppm_api/core/exception_handlers.py +157 -0
  716. trueppm_api/core/export_downloads.py +56 -0
  717. trueppm_api/core/extension_providers.py +56 -0
  718. trueppm_api/core/extension_signals.py +147 -0
  719. trueppm_api/core/idempotent.py +260 -0
  720. trueppm_api/core/metadata.py +42 -0
  721. trueppm_api/core/middleware.py +51 -0
  722. trueppm_api/core/openapi.py +949 -0
  723. trueppm_api/core/password_reset.py +500 -0
  724. trueppm_api/core/protect_conflict.py +100 -0
  725. trueppm_api/core/ratelimit.py +148 -0
  726. trueppm_api/core/redis_throttle.py +82 -0
  727. trueppm_api/core/refresh_cookie_policy.py +156 -0
  728. trueppm_api/core/request_body.py +86 -0
  729. trueppm_api/core/security_checks.py +884 -0
  730. trueppm_api/core/storage_config.py +105 -0
  731. trueppm_api/core/text_decode.py +168 -0
  732. trueppm_api/core/throttling.py +344 -0
  733. trueppm_api/core/valkey.py +214 -0
  734. trueppm_api/core/valkey_cache.py +66 -0
  735. trueppm_api/core/valkey_checks.py +104 -0
  736. trueppm_api/core/valkey_config.py +39 -0
  737. trueppm_api/core/worker_broker_wait.py +149 -0
  738. trueppm_api/core/worker_heartbeat.py +134 -0
  739. trueppm_api/fields.py +36 -0
  740. trueppm_api/permissions.py +30 -0
  741. trueppm_api/py.typed +0 -0
  742. trueppm_api/routing.py +11 -0
  743. trueppm_api/settings/__init__.py +0 -0
  744. trueppm_api/settings/base.py +2455 -0
  745. trueppm_api/settings/dev.py +273 -0
  746. trueppm_api/settings/prod.py +228 -0
  747. trueppm_api/urls.py +153 -0
  748. trueppm_api/workflows/__init__.py +38 -0
  749. trueppm_api/workflows/backends/__init__.py +6 -0
  750. trueppm_api/workflows/backends/default.py +297 -0
  751. trueppm_api/workflows/consumers/__init__.py +11 -0
  752. trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
  753. trueppm_api/workflows/interface.py +148 -0
  754. trueppm_api/workflows/registry.py +106 -0
  755. trueppm_api/workflows/services.py +90 -0
  756. trueppm_api/wsgi.py +11 -0
  757. trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
  758. trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
  759. trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
@@ -0,0 +1,82 @@
1
+ """Enterprise extension signal for object change history.
2
+
3
+ OSS fires ``history_record_created`` after every django-simple-history
4
+ ``HistoricalRecord`` save. The enterprise package connects a receiver here to
5
+ implement unlimited retention, cold-storage export, and immutable audit
6
+ stamping — without any modification to OSS code.
7
+
8
+ Signal contract
9
+ ---------------
10
+ - ``sender`` : the ``Historical<Model>`` class whose row was written —
11
+ ``HistoricalTask``, ``HistoricalProject`` or ``HistoricalDependency``. A
12
+ receiver that wants one model's history connects with
13
+ ``sender=Task.history.model``; one that wants all three connects with no
14
+ sender filter.
15
+ - ``instance`` : the original model instance that was mutated
16
+ - ``history_instance`` : the ``HistoricalRecord`` that was just saved
17
+ - ``history_type`` : ``"+"`` (create), ``"~"`` (update), ``"-"`` (delete)
18
+
19
+ Dispatched **inline**, from the ``post_save`` of the historical row, so it runs
20
+ inside the caller's transaction. ``instance`` is therefore a read-only snapshot
21
+ of a row that may still roll back: receivers must not save it and must defer any
22
+ I/O with ``transaction.on_commit()`` (see ``core/extension_signals.py`` for the
23
+ sender and kwarg conventions this follows).
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import django.dispatch
29
+ from django.db.models.signals import post_save
30
+
31
+ from trueppm_api.core.extension_signals import dispatch_extension_signal
32
+
33
+ history_record_created = django.dispatch.Signal()
34
+
35
+
36
+ def _dispatch_history_record_created(
37
+ sender: type,
38
+ instance: object,
39
+ created: bool,
40
+ **kwargs: object,
41
+ ) -> None:
42
+ """Fire ``history_record_created`` after any HistoricalRecord is saved."""
43
+ # django-simple-history always creates new historical rows (never updates),
44
+ # so we only need the ``created=True`` path.
45
+ if not created:
46
+ return
47
+ # The HistoricalRecord instance carries ``instance`` (the tracked object)
48
+ # and ``history_type`` as attributes.
49
+ tracked = getattr(instance, "instance", None)
50
+ history_type = getattr(instance, "history_type", None)
51
+ # Robust dispatch is load-bearing here, not defensive style: this runs inside
52
+ # the post_save of every Historical* row for Project, Task and Dependency, so
53
+ # a raising enterprise receiver would fail every task save in the product
54
+ # (#2606).
55
+ # sender is the ``Historical<Model>`` class — the row this signal is about,
56
+ # reachable Enterprise-side as ``Task.history.model``. Written as
57
+ # ``type(instance)`` rather than the ``sender`` parameter so the class is
58
+ # visible at the call site: ``sender`` here is post_save's argument and
59
+ # reads as a pass-through of something the dispatcher chose (#3777).
60
+ dispatch_extension_signal(
61
+ history_record_created,
62
+ sender=type(instance),
63
+ instance=tracked,
64
+ history_instance=instance,
65
+ history_type=history_type,
66
+ )
67
+
68
+
69
+ def _connect_to_historical_models() -> None:
70
+ """Wire the receiver to every Historical* model generated by simple_history.
71
+
72
+ Called from HistoryConfig.ready() after all apps are loaded so the
73
+ Historical* models exist in the app registry.
74
+ """
75
+ from trueppm_api.apps.projects.models import Dependency, Project, Task
76
+
77
+ for model in (Project, Task, Dependency):
78
+ historical_model = model.history.model
79
+ post_save.connect(_dispatch_history_record_created, sender=historical_model)
80
+
81
+
82
+ _connect_to_historical_models()
@@ -0,0 +1,105 @@
1
+ """Celery tasks for object change history maintenance."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from datetime import timedelta
7
+ from typing import Any
8
+
9
+ from django.db import OperationalError
10
+ from django.utils import timezone
11
+
12
+ from trueppm_api.core.idempotent import idempotent_task
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def _history_purge_counts(
18
+ *, dry_run: bool = False, override_value: int | None = None
19
+ ) -> dict[str, int]:
20
+ """Return ``{model_name: rows}`` purged (or eligible, when ``dry_run``).
21
+
22
+ The window comes from ``resolve_retention`` (operator override → the
23
+ HISTORY_RETENTION_DAYS default, ADR-0173); ``None`` disables the purge and
24
+ yields an empty dict. ``override_value`` forces a hypothetical window.
25
+ """
26
+ from trueppm_api.apps.observability.retention import resolve_retention
27
+ from trueppm_api.apps.projects.models import Dependency, Project, Task, TaskActivityEvent
28
+
29
+ retention_days = (
30
+ override_value
31
+ if override_value is not None
32
+ else resolve_retention("HISTORY_RETENTION_DAYS")
33
+ )
34
+ if retention_days is None:
35
+ return {}
36
+
37
+ cutoff = timezone.now() - timedelta(days=retention_days)
38
+ totals: dict[str, int] = {}
39
+ for model in (Project, Task, Dependency):
40
+ qs = model.history.filter(history_date__lt=cutoff)
41
+ totals[model.__name__] = qs.count() if dry_run else qs.delete()[0]
42
+ # TaskActivityEvent (ADR-0207) is an append-only per-task audit source backing
43
+ # the same activity feed as the diff history, so it ages out on the SAME
44
+ # HISTORY_RETENTION_DAYS window — keeping the feed's two row sources consistent
45
+ # and bounding the cpm_recalculated growth that a busy schedule generates. It is
46
+ # a plain model (no simple-history), so it is keyed on created_at.
47
+ activity_qs = TaskActivityEvent.objects.filter(created_at__lt=cutoff)
48
+ totals["TaskActivityEvent"] = activity_qs.count() if dry_run else activity_qs.delete()[0]
49
+ return totals
50
+
51
+
52
+ def _do_history_purge(*, dry_run: bool = False, override_value: int | None = None) -> int:
53
+ """Total historical rows purged (or eligible, when ``dry_run``) across all
54
+ history tables. The coordinator-facing entry point (ADR-0173 §C)."""
55
+ return sum(_history_purge_counts(dry_run=dry_run, override_value=override_value).values())
56
+
57
+
58
+ @idempotent_task(
59
+ lock_key_template="history_purge",
60
+ lock_ttl=600,
61
+ on_contention="skip",
62
+ name="history.purge_old_records",
63
+ autoretry_for=(ConnectionError, OperationalError),
64
+ retry_backoff=60,
65
+ retry_backoff_max=600,
66
+ retry_jitter=True,
67
+ max_retries=3,
68
+ soft_time_limit=300,
69
+ time_limit=360,
70
+ acks_late=True,
71
+ )
72
+ def purge_old_history_records(self: object) -> dict[str, Any]:
73
+ """Delete historical records older than the resolved HISTORY retention window.
74
+
75
+ Still dispatchable directly, but no longer on its own Beat schedule — the
76
+ consolidated retention coordinator owns scheduled purging (ADR-0173 §C).
77
+ Returns a summary dict for logging and monitoring.
78
+
79
+ When the window resolves to None the task exits immediately — this disables
80
+ automatic purging for deployments that retain records indefinitely (or archive
81
+ them to cold storage via the history_record_created signal before purging).
82
+ """
83
+ from trueppm_api.apps.observability.retention import resolve_retention
84
+
85
+ retention_days = resolve_retention("HISTORY_RETENTION_DAYS")
86
+ if retention_days is None:
87
+ logger.info("purge_old_history_records: retention disabled, skipping purge")
88
+ return {"status": "skipped", "reason": "unlimited retention"}
89
+
90
+ cutoff = timezone.now() - timedelta(days=retention_days)
91
+ totals = _history_purge_counts()
92
+ for label, deleted in totals.items():
93
+ if deleted:
94
+ logger.info(
95
+ "purge_old_history_records: deleted %d Historical%s rows older than %s",
96
+ deleted,
97
+ label,
98
+ cutoff.date(),
99
+ )
100
+
101
+ total = sum(totals.values())
102
+ logger.info(
103
+ "purge_old_history_records: total %d rows deleted (cutoff=%s)", total, cutoff.date()
104
+ )
105
+ return {"status": "ok", "deleted": totals, "cutoff": cutoff.isoformat()}
@@ -0,0 +1,38 @@
1
+ """URL routing for the history app."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from django.urls import path
6
+
7
+ from trueppm_api.apps.history.views import (
8
+ ProjectChangelogView,
9
+ ProjectHistoryListView,
10
+ )
11
+
12
+ urlpatterns = [
13
+ path(
14
+ "projects/<project_pk>/changelog/",
15
+ ProjectChangelogView.as_view(),
16
+ name="project-changelog",
17
+ ),
18
+ path(
19
+ "projects/<project_pk>/history/",
20
+ ProjectHistoryListView.as_view(),
21
+ name="project-history-list",
22
+ ),
23
+ # NOTE(#3372): ``projects/<project_pk>/history/summary/``
24
+ # (ProjectHistorySummaryView, ``project-history-summary``) was removed
25
+ # outright rather than deprecated — it shipped in 0.1 with a docstring
26
+ # claiming the UI called it on refresh, but had zero client consumers
27
+ # (web uses ``/changelog/`` and task history instead). See ADR-0011's
28
+ # dated Superseded note and stability.md step 3 for the recorded
29
+ # decision and rationale. The path now falls through to Django's
30
+ # standard 404, which is the deliberate outcome for a stale caller.
31
+ #
32
+ # NOTE: ``projects/<project_pk>/tasks/<task_pk>/history/`` is intentionally
33
+ # NOT registered here. The projects app already serves that path via
34
+ # ``project-task-history`` (TaskHistoryView), and because ``projects.urls``
35
+ # is included before ``history.urls`` in the root URLConf, a registration
36
+ # here would be permanently shadowed (dead route). The duplicate was removed
37
+ # per issue #781 so the OpenAPI schema shows a single task-history operation.
38
+ ]
@@ -0,0 +1,443 @@
1
+ """Views for the object change history API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import logging
7
+ from typing import Any
8
+
9
+ from django.contrib.auth import get_user_model
10
+ from django.shortcuts import get_object_or_404
11
+ from django.utils import timezone
12
+ from django.utils.dateparse import parse_datetime
13
+ from drf_spectacular.utils import (
14
+ OpenApiParameter,
15
+ OpenApiResponse,
16
+ extend_schema,
17
+ inline_serializer,
18
+ )
19
+ from rest_framework import serializers as drf_serializers
20
+ from rest_framework.exceptions import ValidationError as DRFValidationError
21
+ from rest_framework.pagination import PageNumberPagination
22
+ from rest_framework.permissions import IsAuthenticated
23
+ from rest_framework.request import Request
24
+ from rest_framework.response import Response
25
+ from rest_framework.views import APIView
26
+
27
+ from trueppm_api.apps.access.models import ProjectMembership, Role
28
+ from trueppm_api.apps.access.permissions import IsProjectMember
29
+ from trueppm_api.apps.history import changelog
30
+ from trueppm_api.apps.history.diff_policy import (
31
+ HISTORY_DIFF_NOISE,
32
+ is_compared,
33
+ visible_changes,
34
+ )
35
+ from trueppm_api.apps.history.serializers import (
36
+ ChangelogResponseSerializer,
37
+ HistoryRecordSerializer,
38
+ )
39
+ from trueppm_api.apps.projects.models import Project, Task
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ User = get_user_model()
44
+
45
+ # Which fields a diff compares, hides, and promotes is decided ONCE, in
46
+ # ``history.diff_policy`` — shared with the task drawer's pipeline in
47
+ # ``apps/projects/views.py`` (#3435). Do not add a local exclusion set here.
48
+
49
+
50
+ class HistoryPagination(PageNumberPagination):
51
+ page_size = 50
52
+ page_size_query_param = "page_size"
53
+ max_page_size = 200
54
+
55
+
56
+ def _build_prev_map(records: list[Any]) -> dict[int, Any | None]:
57
+ """Return history_id → previous HistoricalRecord without extra DB queries.
58
+
59
+ Groups records by their original-object PK, sorts each group by
60
+ history_date ascending, and pairs adjacent records in Python. Avoids
61
+ the per-record DB round-trip that record.prev_record triggers.
62
+ """
63
+ by_obj: dict[Any, list[Any]] = {}
64
+ for r in records:
65
+ by_obj.setdefault(r.id, []).append(r)
66
+ prev_map: dict[int, Any | None] = {}
67
+ for group in by_obj.values():
68
+ group.sort(key=lambda r: r.history_date)
69
+ for i, r in enumerate(group):
70
+ prev_map[r.history_id] = group[i - 1] if i > 0 else None
71
+ return prev_map
72
+
73
+
74
+ @functools.cache
75
+ def _compared_fields(model: Any, *, object_scoped: bool) -> tuple[Any, ...]:
76
+ """The historical model's fields this pipeline compares (``diff_policy``).
77
+
78
+ Keyed by model class, not record: every record in a batch shares one model (a
79
+ per-object list is one model; the changelog diffs one model per source; the
80
+ summary aggregates three single-model batches of up to ``_MAX_HISTORY_ROWS``),
81
+ so re-filtering ~60 fields through the policy per row was pure repeat work.
82
+ The set of historical models is small and static for the process lifetime.
83
+ """
84
+ return tuple(f for f in model._meta.fields if is_compared(f.name, object_scoped=object_scoped))
85
+
86
+
87
+ def _compute_diffs(
88
+ records: list[Any],
89
+ all_records: list[Any] | None = None,
90
+ *,
91
+ object_scoped: bool = True,
92
+ ) -> dict[int, list[dict[str, Any]]]:
93
+ """Return history_id → field-diff list for a batch of HistoricalRecords.
94
+
95
+ Compares each record against its predecessor. Records with no visible change
96
+ map to an empty list; the view omits those from the response so CPM-only
97
+ mutations (or future excluded-field updates) don't produce noise. A change in
98
+ ``HISTORY_DIFF_PROMOTED_WHEN_ALONE`` is kept only when it is the record's sole
99
+ change (#3306) — the same rule the task drawer applies.
100
+
101
+ Each row is keyed by the model **field name** (``assignee``), not the column
102
+ (``assignee_id``): the client's one label map is keyed on names, and the drawer
103
+ pipeline already emits them, so emitting columns here is what left the project
104
+ Activity page rendering raw identifiers (#3435). Values are read through
105
+ ``attname`` so a relation yields its id rather than a lazy fetch per row.
106
+
107
+ ``object_scoped`` says whether the caller renders ONE object's own history
108
+ (``True``: the per-object list views, where the object's tombstone flag is not a
109
+ field change worth a row) or a cross-object stream (``False``: the project
110
+ changelog, where that flag is the only record a soft delete left).
111
+
112
+ Pass all_records (the full unpaginated list for the same object) so that
113
+ the first record on a page can find its predecessor even when it sits on
114
+ a different page.
115
+ """
116
+ prev_map = _build_prev_map(all_records if all_records is not None else records)
117
+ result: dict[int, list[dict[str, Any]]] = {}
118
+ for record in records:
119
+ prev = prev_map.get(record.history_id)
120
+ model: Any = type(record)
121
+ fields = _compared_fields(model, object_scoped=object_scoped)
122
+ if prev is None:
123
+ # Creation — list non-null fields as old=None → new=value. Promotion is
124
+ # a rule about updates, so promotable noise is dropped here outright.
125
+ changes: list[dict[str, Any]] = [
126
+ {"field": f.name, "old": None, "new": getattr(record, f.attname)}
127
+ for f in fields
128
+ if f.name not in HISTORY_DIFF_NOISE and getattr(record, f.attname) is not None
129
+ ]
130
+ else:
131
+ changes = visible_changes(
132
+ (
133
+ {
134
+ "field": f.name,
135
+ "old": getattr(prev, f.attname),
136
+ "new": getattr(record, f.attname),
137
+ }
138
+ for f in fields
139
+ if getattr(record, f.attname) != getattr(prev, f.attname)
140
+ ),
141
+ lambda change: str(change["field"]),
142
+ )
143
+ result[record.history_id] = changes
144
+ return result
145
+
146
+
147
+ def _caller_can_see_user(request: Request, project: Project) -> bool:
148
+ """True if the caller holds Owner or Admin role (>= Role.ADMIN)."""
149
+ try:
150
+ m = ProjectMembership.objects.get(
151
+ user=request.user, # type: ignore[misc]
152
+ project=project,
153
+ is_deleted=False,
154
+ )
155
+ return m.role >= Role.ADMIN
156
+ except ProjectMembership.DoesNotExist:
157
+ return False
158
+
159
+
160
+ # Cap the number of history rows materialized in memory for the per-task and
161
+ # per-project history list views. Without this cap a task with thousands of
162
+ # edits (a busy automated integration, or a re-import loop) would load the full
163
+ # history table into Python on every page request.
164
+ #
165
+ # NOTE(#1889): this cap intentionally differs from the projects app's
166
+ # ``apps.projects.views._MAX_HISTORY_ROWS`` (2000), which backs the wired
167
+ # ``/projects/<pk>/tasks/<pk>/history/`` route serving the task drawer.
168
+ # ``TaskHistoryListView`` below is deliberately unwired (see this app's
169
+ # ``urls.py``); the 5000 bound here covers the project-wide list view. Do not
170
+ # "sync" the two numbers; cross-reference comments live at both constants.
171
+ #
172
+ # NOTE(#3372): this constant previously was also shared with
173
+ # ``ProjectHistorySummaryView``, an orphaned endpoint (zero client consumers
174
+ # since it shipped in 0.1) removed outright rather than deprecated — see
175
+ # ADR-0011's dated Superseded note and stability.md step 3. That class carried
176
+ # its own identical-value class attribute of the same name, which is gone now
177
+ # too; nothing here refers to it any longer.
178
+ _MAX_HISTORY_ROWS = 5000
179
+
180
+
181
+ def _history_page_response(
182
+ request: Request,
183
+ view: APIView,
184
+ project: Project,
185
+ history_queryset: Any,
186
+ ) -> Response:
187
+ """Shared GET body for the per-task and per-project history list views.
188
+
189
+ Fetches cap+1 rows to detect truncation, computes diffs (passing the
190
+ untrimmed cap+1 batch as the diff seed for the oldest kept record, #1889),
191
+ hides ``history_user`` for callers below Owner/Admin, and returns the
192
+ paginated, diff-filtered response with ``count_truncated`` grafted on.
193
+ """
194
+ raw: list[Any] = list(
195
+ history_queryset.order_by("-history_date").select_related("history_user")[
196
+ : _MAX_HISTORY_ROWS + 1
197
+ ]
198
+ )
199
+ count_truncated = len(raw) > _MAX_HISTORY_ROWS
200
+ records = raw[:_MAX_HISTORY_ROWS]
201
+
202
+ paginator = HistoryPagination()
203
+ page: list[Any] = paginator.paginate_queryset(records, request, view=view) or records
204
+
205
+ diffs = _compute_diffs(page, all_records=raw)
206
+ hide_user = not _caller_can_see_user(request, project)
207
+ visible = [r for r in page if diffs.get(r.history_id)]
208
+ serializer = HistoryRecordSerializer(
209
+ visible,
210
+ many=True,
211
+ context={"diffs": diffs, "hide_user": hide_user},
212
+ )
213
+ response = paginator.get_paginated_response(serializer.data)
214
+ response.data["count_truncated"] = count_truncated
215
+ return response
216
+
217
+
218
+ class TaskHistoryListView(APIView):
219
+ """Paginated change history for a single task.
220
+
221
+ GET /api/v1/projects/{project_pk}/tasks/{task_pk}/history/
222
+
223
+ Permissions: any project member (Viewer+) may read. history_user details
224
+ are visible only to Owner/Admin (role >= Role.ADMIN); lower-privilege callers
225
+ receive null for that field.
226
+
227
+ At most ``_MAX_HISTORY_ROWS`` records are materialized; ``count_truncated``
228
+ in the response is ``true`` when the cap was hit so the client can surface
229
+ "showing recent activity" instead of implying a complete record.
230
+ """
231
+
232
+ # IsProjectNotArchived is deliberately omitted: history is a read-only audit
233
+ # surface that must stay accessible after a project is archived. Do not add it
234
+ # "for consistency" — archived projects still need their audit trail readable.
235
+ permission_classes = [IsAuthenticated, IsProjectMember]
236
+
237
+ def get(self, request: Request, project_pk: str, task_pk: str) -> Response:
238
+ project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
239
+ self.check_object_permissions(request, project)
240
+ task = get_object_or_404(Task, pk=task_pk, project_id=project_pk, is_deleted=False)
241
+ return _history_page_response(request, self, project, task.history)
242
+
243
+
244
+ class ProjectHistoryListView(APIView):
245
+ """Paginated change history for a project (project-level fields only).
246
+
247
+ GET /api/v1/projects/{project_pk}/history/
248
+
249
+ At most ``_MAX_HISTORY_ROWS`` records are materialized; ``count_truncated``
250
+ in the response is ``true`` when the cap was hit.
251
+ """
252
+
253
+ # IsProjectNotArchived is deliberately omitted: history is a read-only audit
254
+ # surface that must stay accessible after a project is archived. Do not add it
255
+ # "for consistency" — archived projects still need their audit trail readable.
256
+ permission_classes = [IsAuthenticated, IsProjectMember]
257
+
258
+ @extend_schema(
259
+ # Same class of defect as #2583's two named instances: a plain APIView that
260
+ # builds its paginator inline is invisible to drf-spectacular's auto-wrap
261
+ # heuristic, and with no annotation at all the operation shipped a 200 with
262
+ # no response content whatsoever — a generated SDK gets an untyped body for
263
+ # a paginated endpoint. Declare the envelope, including the non-standard
264
+ # ``count_truncated`` flag the handler grafts onto it below.
265
+ responses={
266
+ 200: inline_serializer(
267
+ name="ProjectHistoryPage",
268
+ fields={
269
+ "count": drf_serializers.IntegerField(),
270
+ "next": drf_serializers.URLField(allow_null=True),
271
+ "previous": drf_serializers.URLField(allow_null=True),
272
+ "results": HistoryRecordSerializer(many=True),
273
+ "count_truncated": drf_serializers.BooleanField(),
274
+ },
275
+ )
276
+ },
277
+ )
278
+ def get(self, request: Request, project_pk: str) -> Response:
279
+ project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
280
+ self.check_object_permissions(request, project)
281
+ return _history_page_response(request, self, project, project.history)
282
+
283
+
284
+ class ProjectChangelogView(APIView):
285
+ """Unified newest-first "what changed" stream for a whole project (ADR-0201).
286
+
287
+ GET /api/v1/projects/{project_pk}/changelog/
288
+ ?since=&object_type=&change_type=&user=&cursor=&page_size=
289
+
290
+ Aggregates every project-scoped ``django-simple-history`` table (Task, Sprint,
291
+ Risk, Project, Dependency, TaskRecurrenceRule, and the three policy singletons)
292
+ into one stream with a **stable keyset cursor** — pass the response's
293
+ ``next_cursor`` back as ``cursor`` to page. The cursor is opaque and safe to
294
+ persist; a malformed cursor returns 400.
295
+
296
+ Permissions: any project member (Viewer+) may read (see ADR-0201 — every
297
+ source's live GET is Viewer+, so membership is a sufficient row gate).
298
+ ``history_user`` is returned only to Owner/Admin (``role >= Role.ADMIN``); the
299
+ ``user=`` filter is likewise honored only for those callers, so the feed can
300
+ never be turned into a per-person activity tracker by a lower-privilege reader.
301
+
302
+ Query params:
303
+ since ISO-8601 datetime; inclusive lower bound on the change time.
304
+ object_type Comma-separated source keys (task, sprint, risk, dependency,
305
+ project, task_recurrence, guardrail_policy,
306
+ signal_privacy_policy, decisions_policy). Default: all.
307
+ change_type Comma-separated: created, updated, deleted. Default: all.
308
+ user A history_user id (Owner/Admin only; ignored otherwise).
309
+ cursor Opaque keyset cursor from a prior response's ``next_cursor``.
310
+ page_size 1..100 (default 50).
311
+ """
312
+
313
+ # IsProjectNotArchived is deliberately omitted: history is a read-only audit
314
+ # surface that must stay accessible after a project is archived (matching the
315
+ # per-object history views above).
316
+ permission_classes = [IsAuthenticated, IsProjectMember]
317
+
318
+ @extend_schema(
319
+ summary="Unified project changelog (aggregated, filterable, cursor-paginated)",
320
+ parameters=[
321
+ OpenApiParameter(
322
+ "since", str, description="Inclusive ISO-8601 lower bound on the change time."
323
+ ),
324
+ OpenApiParameter(
325
+ "object_type",
326
+ str,
327
+ description=(
328
+ "Comma-separated source keys: "
329
+ f"{', '.join(changelog.object_type_choices())}. Default: all."
330
+ ),
331
+ ),
332
+ OpenApiParameter(
333
+ "change_type",
334
+ str,
335
+ description="Comma-separated change types: created, updated, deleted.",
336
+ ),
337
+ OpenApiParameter(
338
+ "user", str, description="Filter to one history_user id (Owner/Admin only)."
339
+ ),
340
+ OpenApiParameter(
341
+ "cursor",
342
+ str,
343
+ description=(
344
+ "Opaque keyset cursor from a prior response's next_cursor. "
345
+ "Stable across concurrent writes; persistable; 400 if malformed."
346
+ ),
347
+ ),
348
+ OpenApiParameter("page_size", int, description="Page size (1..100, default 50)."),
349
+ ],
350
+ responses={200: OpenApiResponse(ChangelogResponseSerializer)},
351
+ )
352
+ def get(self, request: Request, project_pk: str) -> Response:
353
+ project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
354
+ self.check_object_permissions(request, project)
355
+
356
+ since = self._parse_since(request.query_params.get("since"))
357
+ object_types = self._parse_object_types(request.query_params.get("object_type"))
358
+ change_types = self._parse_change_types(request.query_params.get("change_type"))
359
+ page_size = self._parse_page_size(request.query_params.get("page_size"))
360
+
361
+ cursor_raw = request.query_params.get("cursor")
362
+ cursor = changelog.ChangelogCursor.decode(cursor_raw) if cursor_raw else None
363
+
364
+ # history_user visibility gates both the field and the user= filter: only
365
+ # Owner/Admin may see who made a change, so only they may filter by it.
366
+ hide_user = not _caller_can_see_user(request, project)
367
+ user_id = None if hide_user else self._parse_user(request.query_params.get("user"))
368
+
369
+ entries, next_cursor = changelog.build_project_changelog(
370
+ project,
371
+ diff_fn=functools.partial(_compute_diffs, object_scoped=False),
372
+ cursor=cursor,
373
+ since=since,
374
+ object_types=object_types,
375
+ change_types=change_types,
376
+ user_id=user_id,
377
+ page_size=page_size,
378
+ )
379
+
380
+ serializer = ChangelogResponseSerializer(
381
+ {
382
+ "results": entries,
383
+ "next_cursor": next_cursor.encode() if next_cursor is not None else None,
384
+ },
385
+ context={"hide_user": hide_user},
386
+ )
387
+ return Response(serializer.data)
388
+
389
+ @staticmethod
390
+ def _parse_user(raw: str | None) -> int | None:
391
+ # The user model's PK is an integer AutoField, so a non-integer value must
392
+ # yield a clean 400 rather than a 500 when the ORM casts it at query time.
393
+ if not raw:
394
+ return None
395
+ try:
396
+ return int(raw)
397
+ except (TypeError, ValueError) as err:
398
+ raise DRFValidationError({"user": "Must be a user id (integer)."}) from err
399
+
400
+ @staticmethod
401
+ def _parse_since(raw: str | None) -> Any:
402
+ if not raw:
403
+ return None
404
+ parsed = parse_datetime(raw)
405
+ if parsed is None:
406
+ raise DRFValidationError({"since": f"Invalid datetime '{raw}' (expected ISO 8601)."})
407
+ if timezone.is_naive(parsed):
408
+ parsed = timezone.make_aware(parsed, timezone.get_current_timezone())
409
+ return parsed
410
+
411
+ @staticmethod
412
+ def _parse_object_types(raw: str | None) -> set[str] | None:
413
+ if not raw:
414
+ return None
415
+ requested = {t.strip() for t in raw.split(",") if t.strip()}
416
+ valid = set(changelog.object_type_choices())
417
+ unknown = requested - valid
418
+ if unknown:
419
+ raise DRFValidationError(
420
+ {"object_type": f"Unknown object type(s): {', '.join(sorted(unknown))}."}
421
+ )
422
+ return requested
423
+
424
+ @staticmethod
425
+ def _parse_change_types(raw: str | None) -> set[str] | None:
426
+ if not raw:
427
+ return None
428
+ requested = {t.strip() for t in raw.split(",") if t.strip()}
429
+ unknown = requested - changelog.CHANGE_TYPES
430
+ if unknown:
431
+ raise DRFValidationError(
432
+ {"change_type": f"Unknown change type(s): {', '.join(sorted(unknown))}."}
433
+ )
434
+ return requested
435
+
436
+ @staticmethod
437
+ def _parse_page_size(raw: str | None) -> int:
438
+ if not raw:
439
+ return changelog.DEFAULT_PAGE_SIZE
440
+ try:
441
+ return int(raw)
442
+ except (TypeError, ValueError) as err:
443
+ raise DRFValidationError({"page_size": "Must be an integer."}) from err
File without changes
@@ -0,0 +1,11 @@
1
+ """AppConfig for the idempotency app — HTTP Idempotency-Key request dedup."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from django.apps import AppConfig
6
+
7
+
8
+ class IdempotencyConfig(AppConfig):
9
+ name = "trueppm_api.apps.idempotency"
10
+ verbose_name = "Idempotency"
11
+ default_auto_field = "django.db.models.BigAutoField"