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.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"""REST API views for MS Project import/export."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import re
|
|
9
|
+
from typing import Any, cast
|
|
10
|
+
|
|
11
|
+
from django.conf import settings
|
|
12
|
+
from django.db import transaction
|
|
13
|
+
from drf_spectacular.types import OpenApiTypes
|
|
14
|
+
from drf_spectacular.utils import (
|
|
15
|
+
OpenApiExample,
|
|
16
|
+
OpenApiParameter,
|
|
17
|
+
OpenApiResponse,
|
|
18
|
+
extend_schema,
|
|
19
|
+
inline_serializer,
|
|
20
|
+
)
|
|
21
|
+
from rest_framework import serializers, status
|
|
22
|
+
from rest_framework.exceptions import PermissionDenied
|
|
23
|
+
from rest_framework.pagination import PageNumberPagination
|
|
24
|
+
from rest_framework.parsers import MultiPartParser
|
|
25
|
+
from rest_framework.permissions import IsAuthenticated
|
|
26
|
+
from rest_framework.request import Request
|
|
27
|
+
from rest_framework.response import Response
|
|
28
|
+
from rest_framework.views import APIView
|
|
29
|
+
|
|
30
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
31
|
+
from trueppm_api.apps.access.permissions import (
|
|
32
|
+
IsProjectAdmin,
|
|
33
|
+
IsProjectMember,
|
|
34
|
+
IsProjectNotArchived,
|
|
35
|
+
)
|
|
36
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
37
|
+
from trueppm_api.apps.msproject.serializers import ImportProvenancePageSerializer
|
|
38
|
+
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
40
|
+
|
|
41
|
+
# Allowed file extensions. The web format picker only offers .xml today and grays
|
|
42
|
+
# .mpp/.mpx (#128/#120); the server stays authoritative and still accepts .mpp when
|
|
43
|
+
# MPXJ is installed, matching the existing import-into-existing-project endpoint.
|
|
44
|
+
_ALLOWED_EXTENSIONS = {"mpp", "xml"}
|
|
45
|
+
|
|
46
|
+
# Sanitize uploaded filenames (#816) before persistence. UploadedFile.name is
|
|
47
|
+
# attacker-controlled via Content-Disposition: filenames may contain control
|
|
48
|
+
# chars, path components ("../../etc/passwd"), HTML metacharacters, or
|
|
49
|
+
# header-injection sequences. The provenance list endpoint surfaces this field
|
|
50
|
+
# (#799 / MR !429) and any downstream consumer that interpolates it into HTML
|
|
51
|
+
# or a Content-Disposition header becomes a stored-XSS / header-injection sink
|
|
52
|
+
# without this filter. Reject everything but printable ASCII excluding the
|
|
53
|
+
# small set of metacharacters; cap to 255 chars; fall back to "upload.xml" so
|
|
54
|
+
# we never store an empty string after sanitization.
|
|
55
|
+
_FILENAME_BANNED = re.compile(r"[^A-Za-z0-9._\- ()]")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _sanitize_filename(raw: str) -> str:
|
|
59
|
+
"""Return a safe stored form of ``raw``.
|
|
60
|
+
|
|
61
|
+
Strips path components (``os.path.basename``), substitutes anything outside
|
|
62
|
+
the printable-ASCII allow-list with ``_``, collapses runs of whitespace,
|
|
63
|
+
caps length at 255 characters, and falls back to ``upload.xml`` if the
|
|
64
|
+
filtered result is empty.
|
|
65
|
+
"""
|
|
66
|
+
name = os.path.basename(raw or "")
|
|
67
|
+
name = _FILENAME_BANNED.sub("_", name)
|
|
68
|
+
name = re.sub(r"\s+", " ", name).strip()
|
|
69
|
+
name = name[:255]
|
|
70
|
+
return name or "upload.xml"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _read_validated_upload(request: Request) -> tuple[str, str] | Response:
|
|
74
|
+
"""Read the multipart ``file`` field and validate extension + size.
|
|
75
|
+
|
|
76
|
+
Returns ``(filename, file_content_b64)`` on success, or a 400 ``Response``
|
|
77
|
+
describing the rejection (no file / unsupported type / too large). Shared by
|
|
78
|
+
the import-into-existing-project and create-from-import views so both enforce
|
|
79
|
+
the same authoritative limits. The returned ``filename`` is sanitized via
|
|
80
|
+
:func:`_sanitize_filename` (#816).
|
|
81
|
+
"""
|
|
82
|
+
uploaded_file = request.FILES.get("file")
|
|
83
|
+
if not uploaded_file:
|
|
84
|
+
return Response(
|
|
85
|
+
{"detail": "No file provided. Send a multipart form with a 'file' field."},
|
|
86
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
filename = _sanitize_filename(uploaded_file.name or "")
|
|
90
|
+
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
|
|
91
|
+
if ext not in _ALLOWED_EXTENSIONS:
|
|
92
|
+
return Response(
|
|
93
|
+
{"detail": f"Unsupported file type: .{ext}. Allowed: .mpp, .xml"},
|
|
94
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
max_bytes = settings.MSPROJECT_MAX_UPLOAD_MB * 1024 * 1024
|
|
98
|
+
if uploaded_file.size and uploaded_file.size > max_bytes:
|
|
99
|
+
return Response(
|
|
100
|
+
{
|
|
101
|
+
"detail": (
|
|
102
|
+
f"File too large ({uploaded_file.size} bytes). "
|
|
103
|
+
f"Maximum: {settings.MSPROJECT_MAX_UPLOAD_MB} MB."
|
|
104
|
+
)
|
|
105
|
+
},
|
|
106
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
file_content_b64 = base64.b64encode(uploaded_file.read()).decode("ascii")
|
|
110
|
+
return filename, file_content_b64
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _project_name_from_filename(filename: str) -> str:
|
|
114
|
+
"""Derive a provisional project name from the upload filename (ADR-0092).
|
|
115
|
+
|
|
116
|
+
The worker overwrites this with the file's ``<Name>`` header once the file
|
|
117
|
+
parses, so this only has to be a sensible placeholder while the import runs.
|
|
118
|
+
"""
|
|
119
|
+
stem = filename.rsplit("/", 1)[-1].rsplit(".", 1)[0]
|
|
120
|
+
cleaned = stem.replace("_", " ").replace("-", " ").strip()
|
|
121
|
+
return cleaned[:255] or "Imported project"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _check_project_role(user: object, project_pk: str, min_role: int) -> None:
|
|
125
|
+
"""Verify the user has at least min_role on the project.
|
|
126
|
+
|
|
127
|
+
Raises PermissionDenied if the membership does not exist or the role
|
|
128
|
+
is below the required minimum.
|
|
129
|
+
"""
|
|
130
|
+
membership = (
|
|
131
|
+
ProjectMembership.objects.filter(
|
|
132
|
+
project_id=project_pk,
|
|
133
|
+
user=user, # type: ignore[misc]
|
|
134
|
+
is_deleted=False,
|
|
135
|
+
)
|
|
136
|
+
.values_list("role", flat=True)
|
|
137
|
+
.first()
|
|
138
|
+
)
|
|
139
|
+
if membership is None or membership < min_role:
|
|
140
|
+
raise PermissionDenied("You need at least Project Manager role for this action.")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _check_project_member(user: object, project_pk: str) -> None:
|
|
144
|
+
"""Verify the user is a project member (any role)."""
|
|
145
|
+
exists = ProjectMembership.objects.filter(
|
|
146
|
+
project_id=project_pk,
|
|
147
|
+
user=user,
|
|
148
|
+
is_deleted=False, # type: ignore[misc]
|
|
149
|
+
).exists()
|
|
150
|
+
if not exists:
|
|
151
|
+
raise PermissionDenied("You must be a member of this project.")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class MsProjectImportView(IdempotencyMixin, APIView):
|
|
155
|
+
"""Upload and import an MS Project file (.mpp or .xml).
|
|
156
|
+
|
|
157
|
+
Requires project Admin role. The import runs asynchronously via Celery;
|
|
158
|
+
the response includes the celery_task_id for progress tracking.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
# Exempt from the generic Idempotency-Key path (ADR-0170): this is a multipart
|
|
162
|
+
# upload, and the import is already deduped at the table level via ImportRequest.
|
|
163
|
+
idempotency_exempt = True
|
|
164
|
+
# IsProjectAdmin makes the Admin requirement declarative at the DRF layer
|
|
165
|
+
# (OpenAPI-visible, enforced before the body runs); the in-body
|
|
166
|
+
# _check_project_role(..., Role.ADMIN) below remains authoritative as
|
|
167
|
+
# defense-in-depth (#1374, mirrors the #1351 additive-DRF-class pattern).
|
|
168
|
+
permission_classes = [IsAuthenticated, IsProjectAdmin, IsProjectNotArchived]
|
|
169
|
+
parser_classes = [MultiPartParser]
|
|
170
|
+
|
|
171
|
+
@extend_schema(
|
|
172
|
+
summary="Import an MS Project file into an existing project",
|
|
173
|
+
request=OpenApiTypes.BINARY,
|
|
174
|
+
responses={
|
|
175
|
+
202: inline_serializer(
|
|
176
|
+
name="MsProjectImportQueuedResponse",
|
|
177
|
+
fields={
|
|
178
|
+
"detail": serializers.CharField(),
|
|
179
|
+
"import_request_id": serializers.UUIDField(),
|
|
180
|
+
},
|
|
181
|
+
),
|
|
182
|
+
400: OpenApiResponse(description="Missing or invalid upload (wrong type/too large)."),
|
|
183
|
+
403: OpenApiResponse(description="Caller lacks the Admin role on the project."),
|
|
184
|
+
},
|
|
185
|
+
)
|
|
186
|
+
def post(self, request: Request, project_pk: str) -> Response:
|
|
187
|
+
_check_project_role(request.user, project_pk, Role.ADMIN)
|
|
188
|
+
|
|
189
|
+
validated = _read_validated_upload(request)
|
|
190
|
+
if isinstance(validated, Response):
|
|
191
|
+
return validated
|
|
192
|
+
filename, file_content_b64 = validated
|
|
193
|
+
|
|
194
|
+
from trueppm_api.apps.msproject.models import ImportRequest
|
|
195
|
+
from trueppm_api.apps.msproject.services import enqueue_import
|
|
196
|
+
|
|
197
|
+
# Write the outbox row inside an atomic block so the file content is
|
|
198
|
+
# durably committed before any dispatch attempt. If the broker is
|
|
199
|
+
# down, the row stays PENDING and drain_import_queue picks it up
|
|
200
|
+
# within 30 seconds — the caller never sees a 503.
|
|
201
|
+
with transaction.atomic():
|
|
202
|
+
req = ImportRequest.objects.create(
|
|
203
|
+
project_id=project_pk,
|
|
204
|
+
filename=filename,
|
|
205
|
+
file_content_b64=file_content_b64,
|
|
206
|
+
initiated_by_id=request.user.pk,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
req_id = str(req.pk)
|
|
210
|
+
transaction.on_commit(lambda: enqueue_import(req_id))
|
|
211
|
+
|
|
212
|
+
return Response(
|
|
213
|
+
{
|
|
214
|
+
"detail": "Import queued.",
|
|
215
|
+
"import_request_id": req_id,
|
|
216
|
+
},
|
|
217
|
+
status=status.HTTP_202_ACCEPTED,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class CreateProjectFromMsProjectView(IdempotencyMixin, APIView):
|
|
222
|
+
"""Create a NEW project by importing an MS Project file (ADR-0092, #797).
|
|
223
|
+
|
|
224
|
+
Distinct from ``MsProjectImportView``, which imports into an existing project.
|
|
225
|
+
The project shell is created synchronously (named from the filename, dated
|
|
226
|
+
today, optionally assigned to a program, and seeded with the methodology a
|
|
227
|
+
New-project sheet would derive — the program's effective methodology, else
|
|
228
|
+
the workspace default, #3432); tasks import asynchronously via the
|
|
229
|
+
``ImportRequest`` outbox, and the worker overwrites the name/start_date from
|
|
230
|
+
the file header once it parses.
|
|
231
|
+
|
|
232
|
+
RBAC: any authenticated user may create a standalone project (becomes Owner) —
|
|
233
|
+
identical to ``POST /projects/``. Assigning a ``program`` requires program
|
|
234
|
+
Admin, enforced by ``ProjectSerializer.validate_program`` (ADR-0070).
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
# Multipart upload; deduped at the table level. Each upload is a distinct
|
|
238
|
+
# creation by design, so the generic Idempotency-Key path does not apply.
|
|
239
|
+
idempotency_exempt = True
|
|
240
|
+
permission_classes = [IsAuthenticated]
|
|
241
|
+
parser_classes = [MultiPartParser]
|
|
242
|
+
|
|
243
|
+
@extend_schema(
|
|
244
|
+
summary="Create a new project by importing an MS Project file",
|
|
245
|
+
request=OpenApiTypes.BINARY,
|
|
246
|
+
responses={
|
|
247
|
+
202: inline_serializer(
|
|
248
|
+
name="MsProjectCreateProjectQueuedResponse",
|
|
249
|
+
fields={
|
|
250
|
+
"queued": serializers.BooleanField(),
|
|
251
|
+
"project_id": serializers.UUIDField(),
|
|
252
|
+
"import_request_id": serializers.UUIDField(),
|
|
253
|
+
},
|
|
254
|
+
),
|
|
255
|
+
400: OpenApiResponse(
|
|
256
|
+
description="Missing/invalid upload, or program-admin gate failed."
|
|
257
|
+
),
|
|
258
|
+
},
|
|
259
|
+
)
|
|
260
|
+
def post(self, request: Request) -> Response:
|
|
261
|
+
from django.utils import timezone
|
|
262
|
+
|
|
263
|
+
from trueppm_api.apps.msproject.models import ImportRequest
|
|
264
|
+
from trueppm_api.apps.msproject.services import enqueue_import
|
|
265
|
+
from trueppm_api.apps.projects.methodology import resolve_new_project_methodology
|
|
266
|
+
from trueppm_api.apps.projects.serializers import ProjectSerializer
|
|
267
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
268
|
+
|
|
269
|
+
validated = _read_validated_upload(request)
|
|
270
|
+
if isinstance(validated, Response):
|
|
271
|
+
return validated
|
|
272
|
+
filename, file_content_b64 = validated
|
|
273
|
+
|
|
274
|
+
# Build the shell via ProjectSerializer so calendar defaulting and the
|
|
275
|
+
# ADR-0070 program-admin gate apply exactly as on POST /projects/.
|
|
276
|
+
project_payload: dict[str, object] = {
|
|
277
|
+
"name": _project_name_from_filename(filename),
|
|
278
|
+
"start_date": timezone.localdate().isoformat(),
|
|
279
|
+
}
|
|
280
|
+
# `parser_classes = [MultiPartParser]` on this view, so the body is always a
|
|
281
|
+
# QueryDict — a JSON list body is rejected as 415 before the handler runs.
|
|
282
|
+
program_id = cast("dict[str, Any]", request.data).get("program")
|
|
283
|
+
if program_id:
|
|
284
|
+
project_payload["program"] = program_id
|
|
285
|
+
|
|
286
|
+
serializer = ProjectSerializer(data=project_payload, context={"request": request})
|
|
287
|
+
serializer.is_valid(raise_exception=True)
|
|
288
|
+
|
|
289
|
+
# Seed the stored methodology the way the New-project sheet does: from the
|
|
290
|
+
# program's effective methodology when one is assigned, else the workspace
|
|
291
|
+
# default (#3432). Without this the shell lands on the model's HYBRID default
|
|
292
|
+
# regardless of the program it was imported into, and the program-settings
|
|
293
|
+
# hint ("new projects start with this methodology") is false for exactly the
|
|
294
|
+
# projects a migrating PM creates first. Passed through `save()` rather than
|
|
295
|
+
# the request body on purpose: `validate_methodology` refuses any explicit
|
|
296
|
+
# methodology on create while a workspace lock is active, but this value is
|
|
297
|
+
# the server's own resolution (which already honors the lock), not a caller
|
|
298
|
+
# override — a policy refusal against it would be wrong. `program` here is
|
|
299
|
+
# the instance the PrimaryKeyRelatedField already loaded and the ADR-0070
|
|
300
|
+
# gate already vetted, so the resolution adds no per-request query beyond
|
|
301
|
+
# the workspace singleton.
|
|
302
|
+
methodology = resolve_new_project_methodology(serializer.validated_data.get("program"))
|
|
303
|
+
|
|
304
|
+
# Shell + Owner membership + outbox row commit atomically, so a broker
|
|
305
|
+
# hiccup never leaves an ownerless project or an unreferenced import.
|
|
306
|
+
with transaction.atomic():
|
|
307
|
+
project = serializer.save(methodology=methodology)
|
|
308
|
+
ProjectMembership.objects.create(
|
|
309
|
+
project=project,
|
|
310
|
+
user=request.user, # type: ignore[misc]
|
|
311
|
+
role=Role.OWNER,
|
|
312
|
+
)
|
|
313
|
+
req = ImportRequest.objects.create(
|
|
314
|
+
project=project,
|
|
315
|
+
filename=filename,
|
|
316
|
+
file_content_b64=file_content_b64,
|
|
317
|
+
initiated_by_id=request.user.pk,
|
|
318
|
+
creates_project=True,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
project_id = str(project.pk)
|
|
322
|
+
req_id = str(req.pk)
|
|
323
|
+
transaction.on_commit(lambda: enqueue_import(req_id))
|
|
324
|
+
transaction.on_commit(
|
|
325
|
+
lambda: broadcast_board_event(project_id, "project_created", {"id": project_id})
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return Response(
|
|
329
|
+
{"queued": True, "project_id": project_id, "import_request_id": req_id},
|
|
330
|
+
status=status.HTTP_202_ACCEPTED,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
class ImportProvenancePagination(PageNumberPagination):
|
|
335
|
+
"""Page-number pagination for the import-provenance list (#1317).
|
|
336
|
+
|
|
337
|
+
Replaces the previous hard ``[:100]`` slice: a workspace that skipped the
|
|
338
|
+
7-day purge job could accumulate far more, and the slice silently dropped
|
|
339
|
+
the overflow with no way to reach it. ``results`` stays the row key so the
|
|
340
|
+
existing client read is unchanged; ``count``/``next``/``previous`` are added.
|
|
341
|
+
"""
|
|
342
|
+
|
|
343
|
+
page_size = 50
|
|
344
|
+
page_size_query_param = "page_size"
|
|
345
|
+
max_page_size = 200
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class ImportRequestProvenanceListView(APIView):
|
|
349
|
+
"""List ImportRequest rows for a project — the audit / provenance surface (#799).
|
|
350
|
+
|
|
351
|
+
Read-only at Member+ (any role). Returns the recent imports newest first
|
|
352
|
+
with filename, who initiated it, when, status, and task count from the
|
|
353
|
+
linked TaskRun summary. Powers the "Imported from … on … by …" affordance
|
|
354
|
+
on the project Overview and gives Marcus's PMO audit trail a starting
|
|
355
|
+
point without spinning up the enterprise audit overlay.
|
|
356
|
+
|
|
357
|
+
Rows are purged after 7 days by `purge_old_import_requests`, so this is
|
|
358
|
+
a recent-activity view, not durable audit history. Page-paginated (#1317).
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
# IsProjectMember makes the membership gate declarative (was in-body only).
|
|
362
|
+
# IsProjectNotArchived is deliberately omitted: this is a read-only audit /
|
|
363
|
+
# provenance surface that must remain readable after a project is archived
|
|
364
|
+
# (mirrors the History views' archived-read policy).
|
|
365
|
+
permission_classes = [IsAuthenticated, IsProjectMember]
|
|
366
|
+
|
|
367
|
+
@extend_schema(
|
|
368
|
+
# PINNED. Declaring a serializer-backed *list* response below makes
|
|
369
|
+
# drf-spectacular re-derive this operation's id as
|
|
370
|
+
# `v1_projects_imports_list`, and a renamed operationId is a Breaking change
|
|
371
|
+
# under `stability.md` — every generated SDK method for this endpoint would
|
|
372
|
+
# disappear and reappear under a new name. The rename buys nothing here, so
|
|
373
|
+
# the id is pinned and the schema is tightened underneath it (#2942).
|
|
374
|
+
operation_id="v1_projects_imports_retrieve",
|
|
375
|
+
parameters=[
|
|
376
|
+
OpenApiParameter(
|
|
377
|
+
name="page",
|
|
378
|
+
type=OpenApiTypes.INT,
|
|
379
|
+
location=OpenApiParameter.QUERY,
|
|
380
|
+
required=False,
|
|
381
|
+
description="1-based page number.",
|
|
382
|
+
),
|
|
383
|
+
OpenApiParameter(
|
|
384
|
+
name="page_size",
|
|
385
|
+
type=OpenApiTypes.INT,
|
|
386
|
+
location=OpenApiParameter.QUERY,
|
|
387
|
+
required=False,
|
|
388
|
+
description="Rows per page. Default 50, maximum 200.",
|
|
389
|
+
),
|
|
390
|
+
],
|
|
391
|
+
responses={
|
|
392
|
+
200: ImportProvenancePageSerializer,
|
|
393
|
+
401: OpenApiResponse(description="Authentication credentials were not provided."),
|
|
394
|
+
403: OpenApiResponse(description="Not a member of this project."),
|
|
395
|
+
404: OpenApiResponse(
|
|
396
|
+
response=OpenApiTypes.OBJECT,
|
|
397
|
+
description="Project not found.",
|
|
398
|
+
),
|
|
399
|
+
},
|
|
400
|
+
description=(
|
|
401
|
+
"Page of import-request provenance rows for the project, newest first.\n\n"
|
|
402
|
+
"`status` is one of pending / dispatched / done / dead. `initiated_by` is "
|
|
403
|
+
"the user's integer id; `initiated_by_username` carries the name and is "
|
|
404
|
+
"null once that user is deleted. `task_count` comes from the linked "
|
|
405
|
+
"TaskRun summary and is null until the import completes.\n\n"
|
|
406
|
+
"`warnings` lists everything the import did not carry over — MS Project "
|
|
407
|
+
"constraints, deadlines, baselines, priority, work and cost have no "
|
|
408
|
+
"TruePPM column and are reported with per-family task counts. The strings "
|
|
409
|
+
"are prose for a person, so match the leading `Not imported: ` / "
|
|
410
|
+
"`Partially imported: ` marker rather than the whole line. Empty for a "
|
|
411
|
+
"queued or dead import.\n\n"
|
|
412
|
+
"Rows are purged after `TRUEPPM_IMPORT_RETENTION_DAYS` (default 7), so "
|
|
413
|
+
"this is a recent-activity view, not durable audit history."
|
|
414
|
+
),
|
|
415
|
+
examples=[
|
|
416
|
+
OpenApiExample(
|
|
417
|
+
"imports",
|
|
418
|
+
value={
|
|
419
|
+
"count": 1,
|
|
420
|
+
"next": None,
|
|
421
|
+
"previous": None,
|
|
422
|
+
"results": [
|
|
423
|
+
{
|
|
424
|
+
"id": "6f1c2e70-2a1f-4f0e-9a0a-2f6d1c3b4a55",
|
|
425
|
+
"filename": "plan.xml",
|
|
426
|
+
"status": "done",
|
|
427
|
+
"creates_project": False,
|
|
428
|
+
"requested_at": "2026-05-28T14:03:00Z",
|
|
429
|
+
"initiated_by": 17,
|
|
430
|
+
"initiated_by_username": "sarah",
|
|
431
|
+
"task_count": 142,
|
|
432
|
+
"warnings": [
|
|
433
|
+
"Not imported: deadline dates (TruePPM has no "
|
|
434
|
+
"deadline field) — set on 6 of 142 tasks."
|
|
435
|
+
],
|
|
436
|
+
}
|
|
437
|
+
],
|
|
438
|
+
},
|
|
439
|
+
response_only=True,
|
|
440
|
+
),
|
|
441
|
+
],
|
|
442
|
+
)
|
|
443
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
444
|
+
from trueppm_api.apps.msproject.models import ImportRequest
|
|
445
|
+
from trueppm_api.apps.msproject.serializers import ImportRequestProvenanceSerializer
|
|
446
|
+
from trueppm_api.apps.projects.models import Project
|
|
447
|
+
|
|
448
|
+
if not Project.objects.filter(pk=project_pk).exists():
|
|
449
|
+
return Response({"detail": "Project not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
450
|
+
_check_project_member(request.user, project_pk)
|
|
451
|
+
|
|
452
|
+
# Newest first — matches the project-history affordance UX. select_related
|
|
453
|
+
# the user FK so the serializer's username display doesn't trigger an
|
|
454
|
+
# N+1 across the page's row count. The paginator bounds the page; rows
|
|
455
|
+
# are also purged after 7 days, so this stays a recent-activity view.
|
|
456
|
+
qs = (
|
|
457
|
+
ImportRequest.objects.filter(project_id=project_pk)
|
|
458
|
+
.select_related("initiated_by")
|
|
459
|
+
.order_by("-requested_at")
|
|
460
|
+
)
|
|
461
|
+
from trueppm_api.apps.taskruns.models import TaskRun
|
|
462
|
+
|
|
463
|
+
paginator = ImportProvenancePagination()
|
|
464
|
+
page = paginator.paginate_queryset(qs, request, view=self)
|
|
465
|
+
page_rows = list(page) if page is not None else []
|
|
466
|
+
# Batch-load every linked TaskRun for the page in one IN query and fully
|
|
467
|
+
# pre-seed the serializer's memoization cache. The per-row cache only
|
|
468
|
+
# dedups *repeated* celery_task_ids within a response, but each import
|
|
469
|
+
# has a distinct id, so without this seed the serializer would issue one
|
|
470
|
+
# TaskRun SELECT per row. Seed missing ids to None as well so a "no
|
|
471
|
+
# TaskRun yet" row is also a cache hit rather than a fallthrough query.
|
|
472
|
+
task_ids = [r.celery_task_id for r in page_rows if r.celery_task_id]
|
|
473
|
+
# Order oldest-first so the dict's last-write-wins keeps the *newest* run
|
|
474
|
+
# for a duplicate celery_task_id — matching the per-row `.first()` the
|
|
475
|
+
# serializer falls back to (TaskRun.Meta.ordering is `-created_at`). A
|
|
476
|
+
# Celery retry reuses the task id, so duplicates are structurally possible.
|
|
477
|
+
runs_by_id = {
|
|
478
|
+
run.celery_task_id: run
|
|
479
|
+
for run in TaskRun.objects.filter(celery_task_id__in=task_ids).order_by("created_at")
|
|
480
|
+
}
|
|
481
|
+
cache: dict[str, TaskRun | None] = {tid: runs_by_id.get(tid) for tid in task_ids}
|
|
482
|
+
context: dict[str, Any] = {"_taskrun_cache": cache}
|
|
483
|
+
serializer = ImportRequestProvenanceSerializer(page_rows, many=True, context=context)
|
|
484
|
+
return paginator.get_paginated_response(serializer.data)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class MsProjectExportView(APIView):
|
|
488
|
+
"""Export project schedule as MS Project XML.
|
|
489
|
+
|
|
490
|
+
Requires project Member role (viewer or above).
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
# IsProjectMember gates membership declaratively; the in-body
|
|
494
|
+
# _check_project_member below stays as defense-in-depth.
|
|
495
|
+
permission_classes = [IsAuthenticated, IsProjectMember, IsProjectNotArchived]
|
|
496
|
+
|
|
497
|
+
@extend_schema(
|
|
498
|
+
summary="Export the project schedule as MS Project XML",
|
|
499
|
+
# The response is an XML file attachment, not JSON. drf-spectacular emits
|
|
500
|
+
# an empty `content` for the 200 unless the media type is declared via a
|
|
501
|
+
# (status, media_type) response key, leaving codegen/MCP with no media
|
|
502
|
+
# type to bind (#1381).
|
|
503
|
+
responses={
|
|
504
|
+
(200, "application/xml"): OpenApiResponse(
|
|
505
|
+
response=OpenApiTypes.BINARY,
|
|
506
|
+
description="MS Project XML document as a file attachment.",
|
|
507
|
+
)
|
|
508
|
+
},
|
|
509
|
+
)
|
|
510
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
511
|
+
_check_project_member(request.user, project_pk)
|
|
512
|
+
|
|
513
|
+
from trueppm_api.apps.projects.models import Project
|
|
514
|
+
|
|
515
|
+
if not Project.objects.filter(pk=project_pk).exists():
|
|
516
|
+
return Response(
|
|
517
|
+
{"detail": "Project not found."},
|
|
518
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
from trueppm_api.apps.msproject.exporter import export_project_xml
|
|
522
|
+
|
|
523
|
+
xml_bytes = export_project_xml(project_pk)
|
|
524
|
+
|
|
525
|
+
return Response(
|
|
526
|
+
xml_bytes,
|
|
527
|
+
content_type="application/xml",
|
|
528
|
+
headers={
|
|
529
|
+
"Content-Disposition": (f'attachment; filename="project-{project_pk}.xml"'),
|
|
530
|
+
},
|
|
531
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Notifications app — Mention, Notification, NotificationPreference.
|
|
2
|
+
|
|
3
|
+
Implements the unified @mention and notification surface designed in ADR-0075.
|
|
4
|
+
The same Mention/Notification rows back both TaskComment mentions (this MR,
|
|
5
|
+
#311) and future TaskNote mentions (#476). The MentionScope enum stubs the
|
|
6
|
+
visibility gate that #476 will extend for team-private Decisions.
|
|
7
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""App config for the notifications app (ADR-0075)."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class NotificationsConfig(AppConfig):
|
|
7
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
8
|
+
name = "trueppm_api.apps.notifications"
|
|
9
|
+
verbose_name = "Notifications"
|
|
10
|
+
|
|
11
|
+
def ready(self) -> None:
|
|
12
|
+
# Side-effect imports for signal receivers (Mention → Notification fan-out
|
|
13
|
+
# wiring lives in receivers.py; imported here so it's registered exactly
|
|
14
|
+
# once at app-ready time).
|
|
15
|
+
from . import receivers # noqa: F401
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Data-backfill helpers for the notifications app migrations.
|
|
2
|
+
|
|
3
|
+
These functions are extracted from migration files so tests can import them
|
|
4
|
+
without coupling to migration file names, which break on squash (CLAUDE.md rule 3).
|
|
5
|
+
|
|
6
|
+
The valid key sets are inlined rather than imported from the model enums so the
|
|
7
|
+
helpers stay correct even if those enums later change — a data migration must
|
|
8
|
+
reproduce the state at the time it runs.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
_VALID_EVENT_TYPES = {
|
|
16
|
+
"task_assigned",
|
|
17
|
+
"task_overdue",
|
|
18
|
+
"comment_mention",
|
|
19
|
+
"status_change",
|
|
20
|
+
"budget_alert",
|
|
21
|
+
"risk_created",
|
|
22
|
+
"milestone_reached",
|
|
23
|
+
"sprint_start",
|
|
24
|
+
"sprint_end",
|
|
25
|
+
}
|
|
26
|
+
_VALID_CHANNELS = {"in_app", "email", "slack", "mobile_push"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _clean_channel_row(channels: dict[Any, Any]) -> tuple[dict[str, bool], bool]:
|
|
30
|
+
"""Keep only valid ``(channel → bool)`` cells; report whether anything was dropped."""
|
|
31
|
+
row: dict[str, bool] = {}
|
|
32
|
+
changed = False
|
|
33
|
+
for channel, enabled in channels.items():
|
|
34
|
+
if channel not in _VALID_CHANNELS or not isinstance(enabled, bool):
|
|
35
|
+
changed = True
|
|
36
|
+
continue
|
|
37
|
+
row[channel] = enabled
|
|
38
|
+
return row, changed
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _clean_matrix_payload(matrix: dict[Any, Any]) -> tuple[dict[str, dict[str, bool]], bool]:
|
|
42
|
+
"""Strip unknown event-type keys and clean each channel row; report any drop.
|
|
43
|
+
|
|
44
|
+
Split from :func:`_clean_matrix` so the outer per-row iteration keeps the driver
|
|
45
|
+
loop (which persists) readable and each nesting level stays shallow.
|
|
46
|
+
"""
|
|
47
|
+
cleaned: dict[str, dict[str, bool]] = {}
|
|
48
|
+
changed = False
|
|
49
|
+
for event_type, channels in matrix.items():
|
|
50
|
+
if event_type not in _VALID_EVENT_TYPES or not isinstance(channels, dict):
|
|
51
|
+
changed = True
|
|
52
|
+
continue
|
|
53
|
+
row, row_changed = _clean_channel_row(channels)
|
|
54
|
+
changed = changed or row_changed
|
|
55
|
+
cleaned[event_type] = row
|
|
56
|
+
return cleaned, changed
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _clean_matrix(apps: Any, schema_editor: Any) -> None:
|
|
60
|
+
"""Strip unknown event-type / channel keys from persisted notification matrices (#675).
|
|
61
|
+
|
|
62
|
+
The serializer rejects unknown keys on write, but rows created before that
|
|
63
|
+
validation shipped (#522 / #589) may carry garbage keys that leak into the
|
|
64
|
+
GET response and mislead the #674 dispatcher. Drops any event-type or channel
|
|
65
|
+
key not in the current enums, and any non-boolean leaf, leaving only valid cells.
|
|
66
|
+
"""
|
|
67
|
+
Model = apps.get_model("notifications", "ProjectNotificationPreference")
|
|
68
|
+
for pref in Model.objects.all().iterator():
|
|
69
|
+
matrix = pref.matrix
|
|
70
|
+
if not isinstance(matrix, dict):
|
|
71
|
+
# Corrupt non-dict payload — reset to an empty matrix; reads fall
|
|
72
|
+
# through to the default overlay.
|
|
73
|
+
pref.matrix = {}
|
|
74
|
+
pref.save(update_fields=["matrix"])
|
|
75
|
+
continue
|
|
76
|
+
cleaned, changed = _clean_matrix_payload(matrix)
|
|
77
|
+
if changed:
|
|
78
|
+
pref.matrix = cleaned
|
|
79
|
+
pref.save(update_fields=["matrix"])
|