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,396 @@
|
|
|
1
|
+
"""CSV import for the project risk register.
|
|
2
|
+
|
|
3
|
+
issue 223 / ADR-0043 addendum. The symmetric counterpart of the #222 export:
|
|
4
|
+
a CSV produced by "Export CSV" round-trips back through this parser. Parsing and
|
|
5
|
+
validation live here — free of Django request objects — so the logic is unit
|
|
6
|
+
testable in isolation and ``RiskViewSet.import_csv`` stays a thin orchestrator.
|
|
7
|
+
|
|
8
|
+
The accepted columns mirror the export (``riskExport.ts``); the issue's extended
|
|
9
|
+
column set (residual probability/impact, cost/schedule impact, contingency
|
|
10
|
+
reserve) maps to fields the Risk model does not have and is out of scope.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import csv
|
|
16
|
+
import io
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import date, datetime
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from trueppm_api.apps.projects.models import RiskCategory, RiskResponse, RiskStatus
|
|
22
|
+
from trueppm_api.core.text_decode import TextDecodeError, decode_uploaded_text
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from collections.abc import Iterator
|
|
26
|
+
|
|
27
|
+
from django.contrib.auth.models import User
|
|
28
|
+
|
|
29
|
+
# Hard limits — this is a one-time onboarding import, not a bulk pipeline.
|
|
30
|
+
# Anything larger is rejected outright (HTTP 400) rather than partially
|
|
31
|
+
# processed, so a giant paste can't tie up a worker or balloon a transaction.
|
|
32
|
+
MAX_BYTES = 2 * 1024 * 1024 # 2 MB
|
|
33
|
+
MAX_ROWS = 500
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RiskImportError(Exception):
|
|
37
|
+
"""File-level failure — the upload could not be parsed at all.
|
|
38
|
+
|
|
39
|
+
Raised for an undecodable file, a missing required ``Title`` column, an
|
|
40
|
+
empty file, or a row count over :data:`MAX_ROWS`. The view maps this to a
|
|
41
|
+
400 with the message as ``detail``. Row-level problems never raise — they
|
|
42
|
+
accumulate as :class:`ImportIssue` entries instead.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class ImportIssue:
|
|
48
|
+
"""A per-row problem. ``row`` is the spreadsheet line (header = row 1)."""
|
|
49
|
+
|
|
50
|
+
row: int
|
|
51
|
+
field: str
|
|
52
|
+
message: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class RiskDraft:
|
|
57
|
+
"""Validated, ready-to-create values for one risk."""
|
|
58
|
+
|
|
59
|
+
title: str
|
|
60
|
+
description: str
|
|
61
|
+
status: str
|
|
62
|
+
probability: int
|
|
63
|
+
impact: int
|
|
64
|
+
category: str | None
|
|
65
|
+
response: str | None
|
|
66
|
+
mitigation_due_date: date | None
|
|
67
|
+
trigger: str
|
|
68
|
+
contingency: str
|
|
69
|
+
owner: User | None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class ImportPlan:
|
|
74
|
+
"""Outcome of parsing — what to create, and what went wrong per row."""
|
|
75
|
+
|
|
76
|
+
drafts: list[RiskDraft] = field(default_factory=list)
|
|
77
|
+
errors: list[ImportIssue] = field(default_factory=list)
|
|
78
|
+
warnings: list[ImportIssue] = field(default_factory=list)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def skipped(self) -> int:
|
|
82
|
+
"""Number of data rows dropped — those with at least one error."""
|
|
83
|
+
return len({issue.row for issue in self.errors})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _reverse_label_map(choices: list[tuple[str, str]]) -> dict[str, str]:
|
|
87
|
+
"""Map both the stored value and the human label (lowercased) → value.
|
|
88
|
+
|
|
89
|
+
The export writes human labels ("In Progress"); a hand-edited CSV may carry
|
|
90
|
+
raw enum values instead. Accept either so the round-trip is forgiving.
|
|
91
|
+
"""
|
|
92
|
+
mapping: dict[str, str] = {}
|
|
93
|
+
for value, label in choices:
|
|
94
|
+
mapping[value.lower()] = value
|
|
95
|
+
mapping[label.lower()] = value
|
|
96
|
+
return mapping
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
_STATUS_MAP = _reverse_label_map(list(RiskStatus.choices))
|
|
100
|
+
_CATEGORY_MAP = _reverse_label_map(list(RiskCategory.choices))
|
|
101
|
+
_RESPONSE_MAP = _reverse_label_map(list(RiskResponse.choices))
|
|
102
|
+
|
|
103
|
+
# Accepted header spellings → canonical draft key (lowercased + stripped before
|
|
104
|
+
# lookup). Export-only artifacts (ID, Severity) are accepted then ignored so a
|
|
105
|
+
# verbatim export round-trips without "unknown column" noise; the "P"/"I"
|
|
106
|
+
# short headers and their long forms both resolve.
|
|
107
|
+
_HEADER_ALIASES: dict[str, str] = {
|
|
108
|
+
"title": "title",
|
|
109
|
+
"description": "description",
|
|
110
|
+
"status": "status",
|
|
111
|
+
"category": "category",
|
|
112
|
+
"response": "response",
|
|
113
|
+
"p": "probability",
|
|
114
|
+
"probability": "probability",
|
|
115
|
+
"i": "impact",
|
|
116
|
+
"impact": "impact",
|
|
117
|
+
"owner": "owner",
|
|
118
|
+
"mitigation due date": "mitigation_due_date",
|
|
119
|
+
"trigger": "trigger",
|
|
120
|
+
"contingency": "contingency",
|
|
121
|
+
"id": "_ignored",
|
|
122
|
+
"severity": "_ignored",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ISO first (machine-edited), then the export's en-US short format ("Jun 9, 2026")
|
|
126
|
+
# and its long form. Unparseable dates warn rather than fail the whole row.
|
|
127
|
+
_DATE_FORMATS = ("%Y-%m-%d", "%b %d, %Y", "%B %d, %Y")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _parse_date(raw: str) -> date | None:
|
|
131
|
+
for fmt in _DATE_FORMATS:
|
|
132
|
+
try:
|
|
133
|
+
return datetime.strptime(raw, fmt).date()
|
|
134
|
+
except ValueError:
|
|
135
|
+
continue
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _parse_pi(raw: str, label: str, row_num: int, plan: ImportPlan) -> int | None:
|
|
140
|
+
"""Parse a probability/impact cell into 1-5.
|
|
141
|
+
|
|
142
|
+
The Risk model requires both (NOT NULL, no default), but a hand-built CSV
|
|
143
|
+
may leave them blank. Blank → default 1 with a warning. Present-but-invalid
|
|
144
|
+
(non-numeric or out of range) → error and the row is skipped, since a stray
|
|
145
|
+
value here usually means the columns were mis-mapped and guessing a severity
|
|
146
|
+
would be worse than skipping.
|
|
147
|
+
"""
|
|
148
|
+
if not raw:
|
|
149
|
+
plan.warnings.append(ImportIssue(row_num, label, f"{label} is blank — defaulted to 1."))
|
|
150
|
+
return 1
|
|
151
|
+
try:
|
|
152
|
+
parsed = int(raw)
|
|
153
|
+
except ValueError:
|
|
154
|
+
plan.errors.append(
|
|
155
|
+
ImportIssue(row_num, label, f"{label} must be a whole number 1-5, got '{raw}'.")
|
|
156
|
+
)
|
|
157
|
+
return None
|
|
158
|
+
if not 1 <= parsed <= 5:
|
|
159
|
+
plan.errors.append(
|
|
160
|
+
ImportIssue(row_num, label, f"{label} must be between 1 and 5, got {parsed}.")
|
|
161
|
+
)
|
|
162
|
+
return None
|
|
163
|
+
return parsed
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _decode(raw: bytes) -> str:
|
|
167
|
+
"""Decode the upload, refusing a decode that went wrong.
|
|
168
|
+
|
|
169
|
+
Thin translation over :func:`trueppm_api.core.text_decode.decode_uploaded_text`.
|
|
170
|
+
|
|
171
|
+
This used to be a strict ``raw.decode("utf-8-sig")``, which reads as fail-closed
|
|
172
|
+
and is — for a *BOM'd* UTF-16 file. But NUL is a valid UTF-8 code point, so a
|
|
173
|
+
BOM-*less* UTF-16 stream decoded without raising and imported risks with titles
|
|
174
|
+
like ``D\x00a\x00t\x00a\x00 \x00l\x00o\x00s\x00s\x00`` (#2937). Sharing the
|
|
175
|
+
decoder rather than re-copying its guard is the actual fix: this was the same
|
|
176
|
+
defect #2892 had already fixed once, in another app.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
RiskImportError: No codec produced usable spreadsheet text.
|
|
180
|
+
"""
|
|
181
|
+
try:
|
|
182
|
+
return decode_uploaded_text(raw)
|
|
183
|
+
except TextDecodeError as exc:
|
|
184
|
+
raise RiskImportError(str(exc)) from exc
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _read_header(reader: Iterator[list[str]]) -> dict[int, str]:
|
|
188
|
+
"""Consume the header row into a column index → canonical key map.
|
|
189
|
+
|
|
190
|
+
Ignored (``ID``/``Severity``) and unrecognized columns are dropped rather
|
|
191
|
+
than reported, so a verbatim export round-trips without "unknown column"
|
|
192
|
+
noise. Title is the one column the import cannot proceed without.
|
|
193
|
+
"""
|
|
194
|
+
try:
|
|
195
|
+
header = next(reader)
|
|
196
|
+
except StopIteration as exc:
|
|
197
|
+
raise RiskImportError("The file is empty.") from exc
|
|
198
|
+
|
|
199
|
+
col_keys: dict[int, str] = {}
|
|
200
|
+
for idx, name in enumerate(header):
|
|
201
|
+
key = _HEADER_ALIASES.get(name.strip().lower())
|
|
202
|
+
if key and key != "_ignored":
|
|
203
|
+
col_keys[idx] = key
|
|
204
|
+
if "title" not in col_keys.values():
|
|
205
|
+
raise RiskImportError(
|
|
206
|
+
"Missing required 'Title' column. The first row must be a header that "
|
|
207
|
+
"includes Title (an unmodified export works as-is)."
|
|
208
|
+
)
|
|
209
|
+
return col_keys
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _row_values(cells: list[str], col_keys: dict[int, str]) -> dict[str, str]:
|
|
213
|
+
"""Project one CSV line onto its canonical keys, tolerating short rows."""
|
|
214
|
+
return {key: cells[idx].strip() for idx, key in col_keys.items() if idx < len(cells)}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _lookup_choice(
|
|
218
|
+
raw: str,
|
|
219
|
+
mapping: dict[str, str],
|
|
220
|
+
*,
|
|
221
|
+
label: str,
|
|
222
|
+
noun: str,
|
|
223
|
+
consequence: str,
|
|
224
|
+
row_num: int,
|
|
225
|
+
plan: ImportPlan,
|
|
226
|
+
) -> str | None:
|
|
227
|
+
"""Resolve a choice cell against ``mapping``, warning when it doesn't match.
|
|
228
|
+
|
|
229
|
+
Status, Category, and Response share one policy — blank means "not supplied"
|
|
230
|
+
and is silent; a value the map doesn't know is coerced (to the caller's
|
|
231
|
+
fallback) with a warning rather than failing the row, because a single odd
|
|
232
|
+
label is far more likely to be a stale spreadsheet than a mis-mapped file.
|
|
233
|
+
"""
|
|
234
|
+
if not raw:
|
|
235
|
+
return None
|
|
236
|
+
mapped = mapping.get(raw.lower())
|
|
237
|
+
if mapped is None:
|
|
238
|
+
plan.warnings.append(
|
|
239
|
+
ImportIssue(row_num, label, f"Unrecognized {noun} '{raw}' — {consequence}.")
|
|
240
|
+
)
|
|
241
|
+
return mapped
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _resolve_due_date(raw: str, row_num: int, plan: ImportPlan) -> date | None:
|
|
245
|
+
if not raw:
|
|
246
|
+
return None
|
|
247
|
+
parsed = _parse_date(raw)
|
|
248
|
+
if parsed is None:
|
|
249
|
+
plan.warnings.append(
|
|
250
|
+
ImportIssue(row_num, "Mitigation Due Date", f"Couldn't read date '{raw}' — left blank.")
|
|
251
|
+
)
|
|
252
|
+
return parsed
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _resolve_owner(
|
|
256
|
+
raw: str, owner_index: dict[str, User], row_num: int, plan: ImportPlan
|
|
257
|
+
) -> User | None:
|
|
258
|
+
if not raw:
|
|
259
|
+
return None
|
|
260
|
+
owner = owner_index.get(raw.lower())
|
|
261
|
+
if owner is None:
|
|
262
|
+
plan.warnings.append(
|
|
263
|
+
ImportIssue(row_num, "Owner", f"No project member matches '{raw}' — left unassigned.")
|
|
264
|
+
)
|
|
265
|
+
return owner
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _draft_from_row(
|
|
269
|
+
values: dict[str, str],
|
|
270
|
+
row_num: int,
|
|
271
|
+
owner_index: dict[str, User],
|
|
272
|
+
plan: ImportPlan,
|
|
273
|
+
) -> RiskDraft | None:
|
|
274
|
+
"""Build one draft, or return ``None`` when the row has a skipping error.
|
|
275
|
+
|
|
276
|
+
Only two conditions skip a row: a missing Title (nothing to name the risk)
|
|
277
|
+
and an out-of-range P/I (see :func:`_parse_pi`). Everything else coerces
|
|
278
|
+
with a warning so one bad cell can't cost the operator the whole row.
|
|
279
|
+
"""
|
|
280
|
+
title = values.get("title", "")
|
|
281
|
+
if not title:
|
|
282
|
+
plan.errors.append(ImportIssue(row_num, "Title", "Title is required."))
|
|
283
|
+
return None
|
|
284
|
+
|
|
285
|
+
prob = _parse_pi(values.get("probability", ""), "P", row_num, plan)
|
|
286
|
+
imp = _parse_pi(values.get("impact", ""), "I", row_num, plan)
|
|
287
|
+
if prob is None or imp is None:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
status_val = _lookup_choice(
|
|
291
|
+
values.get("status", ""),
|
|
292
|
+
_STATUS_MAP,
|
|
293
|
+
label="Status",
|
|
294
|
+
noun="status",
|
|
295
|
+
consequence="defaulted to Open",
|
|
296
|
+
row_num=row_num,
|
|
297
|
+
plan=plan,
|
|
298
|
+
)
|
|
299
|
+
return RiskDraft(
|
|
300
|
+
title=title[:512], # mirror the model's max_length
|
|
301
|
+
description=values.get("description", ""),
|
|
302
|
+
status=status_val or RiskStatus.OPEN,
|
|
303
|
+
probability=prob,
|
|
304
|
+
impact=imp,
|
|
305
|
+
category=_lookup_choice(
|
|
306
|
+
values.get("category", ""),
|
|
307
|
+
_CATEGORY_MAP,
|
|
308
|
+
label="Category",
|
|
309
|
+
noun="category",
|
|
310
|
+
consequence="left blank",
|
|
311
|
+
row_num=row_num,
|
|
312
|
+
plan=plan,
|
|
313
|
+
),
|
|
314
|
+
response=_lookup_choice(
|
|
315
|
+
values.get("response", ""),
|
|
316
|
+
_RESPONSE_MAP,
|
|
317
|
+
label="Response",
|
|
318
|
+
noun="response",
|
|
319
|
+
consequence="left blank",
|
|
320
|
+
row_num=row_num,
|
|
321
|
+
plan=plan,
|
|
322
|
+
),
|
|
323
|
+
mitigation_due_date=_resolve_due_date(values.get("mitigation_due_date", ""), row_num, plan),
|
|
324
|
+
trigger=values.get("trigger", ""),
|
|
325
|
+
contingency=values.get("contingency", ""),
|
|
326
|
+
owner=_resolve_owner(values.get("owner", ""), owner_index, row_num, plan),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def parse_risk_csv(raw: bytes, owner_index: dict[str, User]) -> ImportPlan:
|
|
331
|
+
"""Parse and validate a risk CSV into a create plan.
|
|
332
|
+
|
|
333
|
+
``owner_index`` maps lowercased UUID / email / username → project-member
|
|
334
|
+
user; it is built by the caller (:func:`build_owner_index`) so this function
|
|
335
|
+
stays DB-free and unit-testable. Owner matching is scoped to that index, so
|
|
336
|
+
a CSV can never assign a risk to a non-member (prevents cross-project
|
|
337
|
+
assignment via a crafted file).
|
|
338
|
+
|
|
339
|
+
Row policy:
|
|
340
|
+
* error (row skipped): missing Title, or P/I present but outside 1-5
|
|
341
|
+
* warning (row imported, value coerced): unrecognized status/category/
|
|
342
|
+
response, unparseable date, unmatched owner, or blank P/I (defaulted)
|
|
343
|
+
|
|
344
|
+
Raises:
|
|
345
|
+
RiskImportError: file-level failure (undecodable, empty, no Title
|
|
346
|
+
column, or more than :data:`MAX_ROWS` rows).
|
|
347
|
+
"""
|
|
348
|
+
reader = csv.reader(io.StringIO(_decode(raw)))
|
|
349
|
+
col_keys = _read_header(reader)
|
|
350
|
+
|
|
351
|
+
plan = ImportPlan()
|
|
352
|
+
data_rows = 0
|
|
353
|
+
for offset, cells in enumerate(reader):
|
|
354
|
+
# Skip wholly blank lines (trailing newline, blank separator rows).
|
|
355
|
+
if not any(cell.strip() for cell in cells):
|
|
356
|
+
continue
|
|
357
|
+
data_rows += 1
|
|
358
|
+
if data_rows > MAX_ROWS:
|
|
359
|
+
raise RiskImportError(
|
|
360
|
+
f"Too many rows (limit {MAX_ROWS}). Split the file and import in batches."
|
|
361
|
+
)
|
|
362
|
+
row_num = offset + 2 # header is row 1; first data row is row 2
|
|
363
|
+
|
|
364
|
+
draft = _draft_from_row(_row_values(cells, col_keys), row_num, owner_index, plan)
|
|
365
|
+
if draft is not None:
|
|
366
|
+
plan.drafts.append(draft)
|
|
367
|
+
|
|
368
|
+
if data_rows == 0:
|
|
369
|
+
raise RiskImportError("No data rows found below the header.")
|
|
370
|
+
|
|
371
|
+
return plan
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def build_owner_index(project_id: str) -> dict[str, User]:
|
|
375
|
+
"""Build the lowercased UUID/email/username → member lookup for owner matching.
|
|
376
|
+
|
|
377
|
+
One query (``select_related('user')``), scoped to active project members so
|
|
378
|
+
an import can never assign a risk to someone outside the project. Imported
|
|
379
|
+
here-locally to avoid a circular import at module load (access ↔ projects).
|
|
380
|
+
"""
|
|
381
|
+
from trueppm_api.apps.access.models import ProjectMembership
|
|
382
|
+
|
|
383
|
+
index: dict[str, User] = {}
|
|
384
|
+
memberships = ProjectMembership.objects.filter(
|
|
385
|
+
project_id=project_id, is_deleted=False
|
|
386
|
+
).select_related("user")
|
|
387
|
+
for membership in memberships:
|
|
388
|
+
user = membership.user
|
|
389
|
+
if user is None:
|
|
390
|
+
continue
|
|
391
|
+
index[str(user.pk).lower()] = user
|
|
392
|
+
if user.email:
|
|
393
|
+
index[user.email.lower()] = user
|
|
394
|
+
if user.username:
|
|
395
|
+
index[user.username.lower()] = user
|
|
396
|
+
return index
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Shared guards for client-minted task row ids (ADR-0772 §Security).
|
|
2
|
+
|
|
3
|
+
Accepting a caller-supplied primary key is a trust-boundary change, and it is
|
|
4
|
+
guarded in exactly four places. Those guards were written first for the offline
|
|
5
|
+
sync push (``apps/sync/upload.py``) and are now also required by the Designer's
|
|
6
|
+
batch endpoint (``TaskBulkView``, #2723). They live here so there is **one**
|
|
7
|
+
implementation rather than two that drift — ADR-0743 §4 is the standing rule, and
|
|
8
|
+
the reason it exists is that enforcement drifted apart before precisely by being
|
|
9
|
+
re-stated in one place and not the other.
|
|
10
|
+
|
|
11
|
+
The two callers differ in how they *report* a failed guard, not in what the guard
|
|
12
|
+
decides:
|
|
13
|
+
|
|
14
|
+
- ``sync.upload`` aborts the whole batch (409 ``SyncIdCollision``, or a skip).
|
|
15
|
+
- ``TaskBulkView`` rejects the single row and applies the rest (207).
|
|
16
|
+
|
|
17
|
+
So this module answers questions and never raises a surface-specific exception.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import uuid
|
|
23
|
+
from typing import TYPE_CHECKING, Any
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from trueppm_api.apps.projects.models import Project, Task
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parse_row_uuid(raw: Any) -> uuid.UUID | None:
|
|
30
|
+
"""Return ``raw`` as a UUID, or ``None`` if it is not one.
|
|
31
|
+
|
|
32
|
+
Guard 1 — well-formedness, and it runs **before any ORM query**. This is not
|
|
33
|
+
cosmetic: an unparseable id reaching ``Task.objects.filter(pk__in=…)`` raises a
|
|
34
|
+
Django-core ``ValidationError``/``ValueError`` at query-build time that DRF does
|
|
35
|
+
not catch, which surfaces as a 500 rather than a 400 (#1730).
|
|
36
|
+
|
|
37
|
+
Accepts any parseable UUID version. Rejecting non-v4 was considered and declined
|
|
38
|
+
in ADR-0772: version bits are self-asserted by the caller and therefore prove
|
|
39
|
+
nothing, project scope is the real boundary, and enforcing it here would diverge
|
|
40
|
+
from the sync path for no security gain.
|
|
41
|
+
"""
|
|
42
|
+
if raw is None:
|
|
43
|
+
return None
|
|
44
|
+
if isinstance(raw, uuid.UUID):
|
|
45
|
+
return raw
|
|
46
|
+
try:
|
|
47
|
+
return uuid.UUID(str(raw))
|
|
48
|
+
except (ValueError, AttributeError, TypeError):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def fetch_project_scoped_tasks(row_ids: set[str], project: Project) -> dict[str, Task]:
|
|
53
|
+
"""Bulk-fetch every row the batch references, scoped to ``project``.
|
|
54
|
+
|
|
55
|
+
Guard 2, first half — the IDOR guard (#887). The scoping is the security
|
|
56
|
+
property: the created-bucket upsert treats a hit as an idempotent re-create and
|
|
57
|
+
applies content after a role check on the **URL-scoped** project. If this lookup
|
|
58
|
+
were unscoped, a caller could mint an id colliding with a task in a project they
|
|
59
|
+
cannot see and mutate it using their role *here* rather than their (possibly
|
|
60
|
+
absent) role *there*.
|
|
61
|
+
|
|
62
|
+
Keyed by ``str(pk)`` because ``in_bulk()``'s UUID keys will not match the string
|
|
63
|
+
ids in a JSON payload. One query, so a large batch is not N round-trips (#809).
|
|
64
|
+
"""
|
|
65
|
+
from trueppm_api.apps.projects.models import Task
|
|
66
|
+
|
|
67
|
+
if not row_ids:
|
|
68
|
+
return {}
|
|
69
|
+
return {str(t.pk): t for t in Task.objects.filter(pk__in=row_ids, project=project)}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def foreign_task_ids(
|
|
73
|
+
candidate_ids: set[str], existing_by_id: dict[str, Task], project: Project
|
|
74
|
+
) -> set[str]:
|
|
75
|
+
"""Of ``candidate_ids``, those that resolve to a task in a **different** project.
|
|
76
|
+
|
|
77
|
+
Guard 2, second half. Only ids absent from the project-scoped fetch can be
|
|
78
|
+
foreign, so probe just those — an all-local batch issues no extra query at all.
|
|
79
|
+
|
|
80
|
+
Callers must report a hit **without asserting that the id exists elsewhere**.
|
|
81
|
+
``TaskBulkView`` uses a non-asserting ``id_unavailable`` rather than an
|
|
82
|
+
``id_collision``: a code meaning *this id exists in a project you cannot see* is
|
|
83
|
+
an existence oracle, and under per-row 207 a caller learns N bits per request
|
|
84
|
+
instead of the one bit the whole-request sync abort leaks. #359 (closed,
|
|
85
|
+
``security``) was decided on exactly those grounds — a foreign UUID must be
|
|
86
|
+
indistinguishable from a permission failure. The remedy is *mint a new UUID and
|
|
87
|
+
retry* either way, so the distinction was never actionable.
|
|
88
|
+
"""
|
|
89
|
+
from trueppm_api.apps.projects.models import Task
|
|
90
|
+
|
|
91
|
+
if not candidate_ids:
|
|
92
|
+
return set()
|
|
93
|
+
unknown_ids = candidate_ids - set(existing_by_id)
|
|
94
|
+
if not unknown_ids:
|
|
95
|
+
return set()
|
|
96
|
+
return {
|
|
97
|
+
str(pk)
|
|
98
|
+
for pk in Task.objects.filter(pk__in=unknown_ids)
|
|
99
|
+
.exclude(project=project)
|
|
100
|
+
.values_list("pk", flat=True)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def is_tombstoned(existing: Task | None) -> bool:
|
|
105
|
+
"""Whether an existing row is a soft-deleted tombstone.
|
|
106
|
+
|
|
107
|
+
Guard 3. A create whose id matches a tombstone is **skipped** — not applied, not
|
|
108
|
+
rejected, no ``server_version`` bump, no broadcast (#1730). ``is_deleted`` is not
|
|
109
|
+
writable so the row could never be resurrected even without this guard; the guard
|
|
110
|
+
exists so the re-create branch does not run a full serializer save on a dead row
|
|
111
|
+
and emit a spurious ``task_updated``.
|
|
112
|
+
"""
|
|
113
|
+
return existing is not None and bool(existing.is_deleted)
|