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,667 @@
|
|
|
1
|
+
"""Service-layer helpers for the access app.
|
|
2
|
+
|
|
3
|
+
The service layer wraps multi-row transactions that must remain atomic — most
|
|
4
|
+
importantly the Program create flow, which inserts a ``Program`` row and an
|
|
5
|
+
OWNER ``ProgramMembership`` row in the same transaction so a Program can never
|
|
6
|
+
exist in the database without at least one Owner.
|
|
7
|
+
|
|
8
|
+
ADR-0070: see §Durable Execution §4 for the atomic-create rationale.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import uuid
|
|
14
|
+
from collections.abc import Sequence
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from django.core.exceptions import ValidationError
|
|
18
|
+
from django.db import models, transaction
|
|
19
|
+
from django.utils import timezone
|
|
20
|
+
|
|
21
|
+
from trueppm_api.apps.access.models import ProgramMembership, ProjectMembership, Role
|
|
22
|
+
from trueppm_api.apps.projects.models import Methodology, Program, Project
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _protecting_relations(model: type[models.Model]) -> list[Any]:
|
|
26
|
+
"""Reverse relations into *model* whose FK is ``on_delete=PROTECT``.
|
|
27
|
+
|
|
28
|
+
Derived from ``_meta`` rather than hand-listed. Every hand-maintained version of
|
|
29
|
+
this list in the codebase went stale as new ``PROTECT`` FKs landed — three delete
|
|
30
|
+
paths were each written against the FKs that existed at the time and silently
|
|
31
|
+
started raising ``ProtectedError`` later (#2364). Reflection cannot drift.
|
|
32
|
+
|
|
33
|
+
``include_hidden=True`` is load-bearing, not defensive: a FK declared with
|
|
34
|
+
``related_name="+"`` is absent from ``_meta.related_objects`` but Django's
|
|
35
|
+
deletion collector still enforces its ``PROTECT``. Walking the public
|
|
36
|
+
``related_objects`` would silently miss exactly those — reproducing the bug this
|
|
37
|
+
helper exists to prevent, just one layer down. ``Workspace.calendar`` is a live
|
|
38
|
+
example of a ``+``-named ``PROTECT`` FK in this codebase.
|
|
39
|
+
|
|
40
|
+
``test_protect_relations_snapshot`` pins the resolved set, so adding a new
|
|
41
|
+
``PROTECT`` FK fails a test and forces a deliberate call on whether a hard delete
|
|
42
|
+
should purge that child or refuse — rather than silently purging it here.
|
|
43
|
+
"""
|
|
44
|
+
return [
|
|
45
|
+
rel
|
|
46
|
+
for rel in model._meta.get_fields(include_hidden=True)
|
|
47
|
+
if rel.is_relation
|
|
48
|
+
and rel.auto_created
|
|
49
|
+
and not rel.concrete
|
|
50
|
+
and getattr(rel, "on_delete", None) is models.PROTECT
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _purge_protecting_children(model: type[models.Model], pks: Sequence[Any]) -> None:
|
|
55
|
+
"""Delete every ``PROTECT``-ing child of *pks* so the parent rows can be removed.
|
|
56
|
+
|
|
57
|
+
Only for paths where the caller has already confirmed a permanent, irreversible
|
|
58
|
+
delete (the archive → ``?force=true`` dialog, sample-data teardown, the retention
|
|
59
|
+
purge). Everywhere else a ``PROTECT`` FK should surface as a 409 refusal, not be
|
|
60
|
+
bulldozed — see :mod:`trueppm_api.core.protect_conflict`.
|
|
61
|
+
"""
|
|
62
|
+
if not pks:
|
|
63
|
+
return
|
|
64
|
+
for rel in _protecting_relations(model):
|
|
65
|
+
rel.related_model._base_manager.filter(**{f"{rel.field.name}__in": pks}).delete()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@transaction.atomic
|
|
69
|
+
def hard_delete_projects(project_ids: Sequence[Any]) -> int:
|
|
70
|
+
"""Permanently delete projects and the rows whose ``PROTECT`` FKs block them.
|
|
71
|
+
|
|
72
|
+
Returns the number of ``Project`` rows removed. Callers are responsible for
|
|
73
|
+
authorization, audit events, and sync broadcasts — this helper owns only the
|
|
74
|
+
delete order.
|
|
75
|
+
"""
|
|
76
|
+
if not project_ids:
|
|
77
|
+
return 0
|
|
78
|
+
_purge_protecting_children(Project, project_ids)
|
|
79
|
+
_, by_model = Project.objects.filter(pk__in=project_ids).delete()
|
|
80
|
+
return int(by_model.get(Project._meta.label, 0))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@transaction.atomic
|
|
84
|
+
def hard_delete_program(program_id: uuid.UUID | str) -> None:
|
|
85
|
+
"""Permanently delete a program, its projects, and all ``PROTECT``-ing children.
|
|
86
|
+
|
|
87
|
+
The program's projects are deleted outright rather than detached — the only
|
|
88
|
+
caller is sample-data teardown, where the whole subtree is disposable. Contrast
|
|
89
|
+
:func:`delete_program_cascade`, which *soft*-deletes a real program and detaches
|
|
90
|
+
its projects so they survive as standalone work.
|
|
91
|
+
"""
|
|
92
|
+
project_ids = list(Project.objects.filter(program_id=program_id).values_list("pk", flat=True))
|
|
93
|
+
hard_delete_projects(project_ids)
|
|
94
|
+
_purge_protecting_children(Program, [program_id])
|
|
95
|
+
Program.objects.filter(pk=program_id).delete()
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@transaction.atomic
|
|
99
|
+
def create_program(
|
|
100
|
+
*,
|
|
101
|
+
name: str,
|
|
102
|
+
description: str,
|
|
103
|
+
methodology: str | Methodology,
|
|
104
|
+
created_by: Any,
|
|
105
|
+
lead: Any | None = None,
|
|
106
|
+
) -> Program:
|
|
107
|
+
"""Create a ``Program`` and the creator's OWNER membership in one transaction.
|
|
108
|
+
|
|
109
|
+
Atomicity is the whole point of this helper — if the OWNER ``ProgramMembership``
|
|
110
|
+
INSERT were a second request (or a non-atomic follow-up), a crash between the
|
|
111
|
+
two writes would leave a Program with no Owner, which permission classes treat
|
|
112
|
+
as "nobody can manage this program." The ``@transaction.atomic`` wrapper makes
|
|
113
|
+
that orphan window impossible.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
name: Display name (max 255). No uniqueness enforcement — two PMs can
|
|
117
|
+
both have a program called "Phase 2".
|
|
118
|
+
description: Optional rich text; empty string allowed.
|
|
119
|
+
methodology: One of ``Methodology`` values; default ``HYBRID``.
|
|
120
|
+
created_by: The user creating the program — recorded on the Program row
|
|
121
|
+
and inserted as the OWNER ``ProgramMembership``.
|
|
122
|
+
lead: Optional program lead (#2025). At create time the only member that
|
|
123
|
+
will exist is ``created_by`` (the OWNER row inserted below), so the
|
|
124
|
+
serializer only admits ``lead == created_by`` — the "lead must be a
|
|
125
|
+
member" invariant holds because the OWNER membership is written in the
|
|
126
|
+
same atomic transaction. Defaults to ``None`` (no lead).
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
The persisted ``Program``. The OWNER membership exists by the time the
|
|
130
|
+
function returns.
|
|
131
|
+
"""
|
|
132
|
+
program = Program.objects.create(
|
|
133
|
+
name=name,
|
|
134
|
+
description=description,
|
|
135
|
+
methodology=methodology,
|
|
136
|
+
created_by=created_by,
|
|
137
|
+
lead=lead,
|
|
138
|
+
)
|
|
139
|
+
ProgramMembership.objects.create(
|
|
140
|
+
program=program,
|
|
141
|
+
user=created_by,
|
|
142
|
+
role=Role.OWNER,
|
|
143
|
+
)
|
|
144
|
+
return program
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@transaction.atomic
|
|
148
|
+
def delete_program_cascade(program_id: uuid.UUID | str) -> None:
|
|
149
|
+
"""Soft-delete all memberships, detach all projects, soft-delete the program.
|
|
150
|
+
|
|
151
|
+
Three things must happen atomically:
|
|
152
|
+
|
|
153
|
+
1. **Memberships are soft-deleted.** ``ProgramMembership.program`` is ``PROTECT``,
|
|
154
|
+
so a naive ``program.delete()`` raises ``ProtectedError`` while any member
|
|
155
|
+
rows exist. The UX brief surfaces this as a combined "Remove all members
|
|
156
|
+
and delete" action; the cascade implements that intent server-side.
|
|
157
|
+
2. **Projects are detached.** Django's ``SET_NULL`` only fires on a hard DELETE,
|
|
158
|
+
not on our soft-delete (``is_deleted=True``). Without an explicit detach
|
|
159
|
+
step, ``Project.program_id`` would continue to reference a soft-deleted
|
|
160
|
+
Program — the UI would display "Phase 2 (deleted)" for those projects
|
|
161
|
+
and the program shell would still try to retrieve a 404'd parent. Setting
|
|
162
|
+
``program=NULL`` here matches the dialog's promise that projects become
|
|
163
|
+
standalone after deletion.
|
|
164
|
+
3. **The program itself is soft-deleted.** This bumps ``server_version`` so
|
|
165
|
+
sync consumers can drop the row from their caches.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
program_id: The program to delete.
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
Program.DoesNotExist: if the program is missing or already soft-deleted.
|
|
172
|
+
"""
|
|
173
|
+
# Local import: Project is in the projects app; importing at module top
|
|
174
|
+
# would create a circular dependency through the projects → access path.
|
|
175
|
+
from trueppm_api.apps.projects.models import Project
|
|
176
|
+
|
|
177
|
+
# Lock the program row so concurrent member-add / project-assign requests
|
|
178
|
+
# cannot land between the cascade steps and re-create a reference to the
|
|
179
|
+
# program we're about to mark deleted.
|
|
180
|
+
program = Program.objects.select_for_update().get(pk=program_id, is_deleted=False)
|
|
181
|
+
|
|
182
|
+
for membership in ProgramMembership.objects.select_for_update().filter(
|
|
183
|
+
program=program, is_deleted=False
|
|
184
|
+
):
|
|
185
|
+
membership.soft_delete()
|
|
186
|
+
|
|
187
|
+
# Detach projects — they hold a SET_NULL FK, but SET_NULL only fires on
|
|
188
|
+
# hard delete. soft_delete() on the program would otherwise leave dangling
|
|
189
|
+
# references. We must go through ``Project.save()`` (not ``update()``) so
|
|
190
|
+
# ``server_version`` increments and mobile sync clients pick up the change.
|
|
191
|
+
for project in Project.objects.select_for_update().filter(program=program):
|
|
192
|
+
project.program = None
|
|
193
|
+
project.save(update_fields=["program"])
|
|
194
|
+
|
|
195
|
+
program.soft_delete()
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@transaction.atomic
|
|
199
|
+
def transfer_project_ownership(
|
|
200
|
+
*,
|
|
201
|
+
project: Project,
|
|
202
|
+
new_owner: Any,
|
|
203
|
+
actor: Any,
|
|
204
|
+
) -> ProjectMembership:
|
|
205
|
+
"""Atomically promote a project member to OWNER, demote the actor to ADMIN.
|
|
206
|
+
|
|
207
|
+
Why the target must already be a member: requiring an existing membership
|
|
208
|
+
forces an explicit invite step and gives the audit trail a clean
|
|
209
|
+
"joined → promoted" sequence. Auto-creating membership on transfer would
|
|
210
|
+
surprise the target with admin authority on a project they were never
|
|
211
|
+
knowingly added to.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
project: The project whose ownership is being transferred.
|
|
215
|
+
new_owner: The user who will become OWNER.
|
|
216
|
+
actor: The current OWNER initiating the transfer; will be downgraded
|
|
217
|
+
to ADMIN. May be the same user as ``new_owner`` — in that case
|
|
218
|
+
both upserts are no-ops and the function returns the actor's row.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
The new OWNER's ``ProjectMembership`` row.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
ValidationError: if ``new_owner`` has no existing active membership
|
|
225
|
+
on the project.
|
|
226
|
+
"""
|
|
227
|
+
# Defense in depth — the view layer gates with ``IsProjectOwner``, but the
|
|
228
|
+
# service is reusable (management commands, signals, future endpoints). If
|
|
229
|
+
# the actor is not actually the OWNER, a naive ``.update()`` is a silent
|
|
230
|
+
# no-op that would promote the target without removing anyone — creating
|
|
231
|
+
# an unauthorized OWNER. Assert and fail loudly before any state changes.
|
|
232
|
+
try:
|
|
233
|
+
actor_row = ProjectMembership.objects.select_for_update().get(
|
|
234
|
+
project=project,
|
|
235
|
+
user=actor,
|
|
236
|
+
is_deleted=False,
|
|
237
|
+
)
|
|
238
|
+
except ProjectMembership.DoesNotExist as exc:
|
|
239
|
+
raise ValidationError("Only an existing project Owner can transfer ownership.") from exc
|
|
240
|
+
if actor_row.role != Role.OWNER:
|
|
241
|
+
raise ValidationError("Only an existing project Owner can transfer ownership.")
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
target = ProjectMembership.objects.select_for_update().get(
|
|
245
|
+
project=project,
|
|
246
|
+
user=new_owner,
|
|
247
|
+
is_deleted=False,
|
|
248
|
+
)
|
|
249
|
+
except ProjectMembership.DoesNotExist as exc:
|
|
250
|
+
raise ValidationError(
|
|
251
|
+
"The new owner must already be a member of this project. "
|
|
252
|
+
"Invite them first, then retry the transfer."
|
|
253
|
+
) from exc
|
|
254
|
+
|
|
255
|
+
if new_owner == actor:
|
|
256
|
+
return target
|
|
257
|
+
|
|
258
|
+
# Demote the actor first so the OWNER count never exceeds the previous
|
|
259
|
+
# state mid-transaction — defensive against future single-owner constraints.
|
|
260
|
+
# role_changed_at is stamped alongside role on both rows so the per-project
|
|
261
|
+
# access-evidence timestamp (#590) stays accurate on this path too, not just
|
|
262
|
+
# on the partial_update endpoint.
|
|
263
|
+
now = timezone.now()
|
|
264
|
+
actor_row.role = Role.ADMIN
|
|
265
|
+
actor_row.role_changed_at = now
|
|
266
|
+
actor_row.save(update_fields=["role", "role_changed_at"])
|
|
267
|
+
|
|
268
|
+
target.role = Role.OWNER
|
|
269
|
+
target.role_changed_at = now
|
|
270
|
+
target.save(update_fields=["role", "role_changed_at"])
|
|
271
|
+
return target
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@transaction.atomic
|
|
275
|
+
def transfer_program_sponsorship(
|
|
276
|
+
*,
|
|
277
|
+
program: Program,
|
|
278
|
+
new_owner: Any,
|
|
279
|
+
actor: Any,
|
|
280
|
+
new_lead: Any | None = None,
|
|
281
|
+
) -> ProgramMembership:
|
|
282
|
+
"""Atomically promote a program member to OWNER and optionally rotate the lead.
|
|
283
|
+
|
|
284
|
+
Programs carry both an OWNER membership (RBAC) and a ``lead`` FK (the
|
|
285
|
+
"Program Manager" displayed in the header). Both can move together in a
|
|
286
|
+
sponsorship transfer so the header chip and the access matrix stay in sync.
|
|
287
|
+
|
|
288
|
+
Args:
|
|
289
|
+
program: The program whose sponsorship is being transferred.
|
|
290
|
+
new_owner: The user who will become OWNER.
|
|
291
|
+
actor: The current OWNER initiating the transfer; will be downgraded
|
|
292
|
+
to ADMIN.
|
|
293
|
+
new_lead: Optional user to set as ``program.lead``. ``None`` leaves
|
|
294
|
+
the existing lead unchanged; pass ``new_owner`` explicitly to keep
|
|
295
|
+
owner+lead in lockstep.
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
The new OWNER's ``ProgramMembership`` row.
|
|
299
|
+
|
|
300
|
+
Raises:
|
|
301
|
+
ValidationError: if ``new_owner`` has no existing active program
|
|
302
|
+
membership.
|
|
303
|
+
"""
|
|
304
|
+
# Defense in depth (see ``transfer_project_ownership`` for full rationale)
|
|
305
|
+
# — the actor must currently hold the OWNER row, otherwise the demote
|
|
306
|
+
# ``.update()`` is a no-op and the target gets promoted without anyone
|
|
307
|
+
# being removed.
|
|
308
|
+
try:
|
|
309
|
+
actor_row = ProgramMembership.objects.select_for_update().get(
|
|
310
|
+
program=program,
|
|
311
|
+
user=actor,
|
|
312
|
+
is_deleted=False,
|
|
313
|
+
)
|
|
314
|
+
except ProgramMembership.DoesNotExist as exc:
|
|
315
|
+
raise ValidationError("Only an existing Program Admin can transfer sponsorship.") from exc
|
|
316
|
+
if actor_row.role != Role.OWNER:
|
|
317
|
+
raise ValidationError("Only an existing Program Admin can transfer sponsorship.")
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
target = ProgramMembership.objects.select_for_update().get(
|
|
321
|
+
program=program,
|
|
322
|
+
user=new_owner,
|
|
323
|
+
is_deleted=False,
|
|
324
|
+
)
|
|
325
|
+
except ProgramMembership.DoesNotExist as exc:
|
|
326
|
+
raise ValidationError(
|
|
327
|
+
"The new sponsor must already be a member of this program. "
|
|
328
|
+
"Invite them first, then retry the transfer."
|
|
329
|
+
) from exc
|
|
330
|
+
|
|
331
|
+
if new_owner != actor:
|
|
332
|
+
# role_changed_at is stamped alongside role on both rows so the
|
|
333
|
+
# per-program access-evidence timestamp (#878) stays accurate on this
|
|
334
|
+
# path too, not just on the partial_update endpoint.
|
|
335
|
+
now = timezone.now()
|
|
336
|
+
actor_row.role = Role.ADMIN
|
|
337
|
+
actor_row.role_changed_at = now
|
|
338
|
+
actor_row.save(update_fields=["role", "role_changed_at"])
|
|
339
|
+
|
|
340
|
+
target.role = Role.OWNER
|
|
341
|
+
target.role_changed_at = now
|
|
342
|
+
target.save(update_fields=["role", "role_changed_at"])
|
|
343
|
+
|
|
344
|
+
if new_lead is not None:
|
|
345
|
+
program.lead = new_lead
|
|
346
|
+
program.save(update_fields=["lead"])
|
|
347
|
+
|
|
348
|
+
return target
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@transaction.atomic
|
|
352
|
+
def split_program(
|
|
353
|
+
*,
|
|
354
|
+
program: Program,
|
|
355
|
+
splits: list[dict[str, Any]],
|
|
356
|
+
actor: Any,
|
|
357
|
+
) -> list[Program]:
|
|
358
|
+
"""Split a program into sub-programs and close the original (ADR-0156, #967).
|
|
359
|
+
|
|
360
|
+
For each entry in ``splits`` a new sub-program is created (owned by ``actor``,
|
|
361
|
+
methodology copied from the parent, all inheritable overrides left NULL so the
|
|
362
|
+
new program inherits workspace defaults), and the entry's projects are moved
|
|
363
|
+
under it. After every split is applied the original program is closed — it
|
|
364
|
+
becomes a read-only shell holding any projects that were not redistributed.
|
|
365
|
+
|
|
366
|
+
Only the ``Project.program`` FK moves; tasks, dependencies, baselines,
|
|
367
|
+
memberships, and history are untouched, so each project keeps its full
|
|
368
|
+
schedule and audit trail under its new program. Reassignment goes through
|
|
369
|
+
``Project.save()`` (not ``.update()``) so ``server_version`` increments and
|
|
370
|
+
mobile sync clients pick up the move — same rationale as
|
|
371
|
+
``delete_program_cascade``.
|
|
372
|
+
|
|
373
|
+
The whole operation is one transaction: either every sub-program, every
|
|
374
|
+
reassignment, and the parent close commit together, or nothing does. A bad
|
|
375
|
+
payload raises before any INSERT so the error message is precise.
|
|
376
|
+
|
|
377
|
+
Args:
|
|
378
|
+
program: The program being split. Must be open — callers gate on
|
|
379
|
+
``IsProgramNotClosed``; once closed by this call a replayed request
|
|
380
|
+
is rejected by that gate, which is what makes a network retry safe.
|
|
381
|
+
splits: ``[{"name": str, "project_ids": [uuid]}, ...]``. Each entry
|
|
382
|
+
becomes one sub-program. ``project_ids`` may be empty (an empty
|
|
383
|
+
sub-program shell, symmetric with ``create_program``).
|
|
384
|
+
actor: The current OWNER initiating the split; becomes OWNER of every
|
|
385
|
+
sub-program.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
The created sub-programs, in input order.
|
|
389
|
+
|
|
390
|
+
Raises:
|
|
391
|
+
ValidationError: if a referenced project is not a live member of this
|
|
392
|
+
program, or if a project appears in more than one split.
|
|
393
|
+
"""
|
|
394
|
+
# Lock the parent row so a concurrent project-assign / delete cannot land
|
|
395
|
+
# between validation and reassignment (mirrors delete_program_cascade).
|
|
396
|
+
program = Program.objects.select_for_update().get(pk=program.pk)
|
|
397
|
+
|
|
398
|
+
member_project_ids = {
|
|
399
|
+
str(pk)
|
|
400
|
+
for pk in Project.objects.filter(program=program, is_deleted=False).values_list(
|
|
401
|
+
"pk", flat=True
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
# Validate the full payload up front: every id must be a live member of this
|
|
406
|
+
# program, and no id may be claimed by two sub-programs.
|
|
407
|
+
seen: set[str] = set()
|
|
408
|
+
normalized: list[tuple[str, list[str]]] = []
|
|
409
|
+
for entry in splits:
|
|
410
|
+
name = entry["name"]
|
|
411
|
+
ids = [str(pid) for pid in entry["project_ids"]]
|
|
412
|
+
for pid in ids:
|
|
413
|
+
if pid not in member_project_ids:
|
|
414
|
+
raise ValidationError(f"Project {pid} is not a project of this program.")
|
|
415
|
+
if pid in seen:
|
|
416
|
+
raise ValidationError(f"Project {pid} is assigned to more than one sub-program.")
|
|
417
|
+
seen.add(pid)
|
|
418
|
+
normalized.append((name, ids))
|
|
419
|
+
|
|
420
|
+
sub_programs: list[Program] = []
|
|
421
|
+
for name, ids in normalized:
|
|
422
|
+
sub = create_program(
|
|
423
|
+
name=name,
|
|
424
|
+
description="",
|
|
425
|
+
methodology=program.methodology,
|
|
426
|
+
created_by=actor,
|
|
427
|
+
)
|
|
428
|
+
# Filter by program=program as well as pk so a project moved out by a
|
|
429
|
+
# racing request (despite the lock) is never silently captured.
|
|
430
|
+
for project in Project.objects.select_for_update().filter(
|
|
431
|
+
program=program, pk__in=ids, is_deleted=False
|
|
432
|
+
):
|
|
433
|
+
project.program = sub
|
|
434
|
+
project.save(update_fields=["program"])
|
|
435
|
+
sub_programs.append(sub)
|
|
436
|
+
|
|
437
|
+
if not program.is_closed:
|
|
438
|
+
program.is_closed = True
|
|
439
|
+
program.closed_at = timezone.now()
|
|
440
|
+
program.closed_by = actor
|
|
441
|
+
program.save(update_fields=["is_closed", "closed_at", "closed_by"])
|
|
442
|
+
|
|
443
|
+
return sub_programs
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def revoke_all_refresh_tokens(user: Any) -> int:
|
|
447
|
+
"""Blacklist every outstanding simplejwt refresh token for ``user``.
|
|
448
|
+
|
|
449
|
+
This is the "sign out every device" primitive (ADR-0209). A password reset
|
|
450
|
+
(and, later, any explicit account-security action) must invalidate all of the
|
|
451
|
+
user's existing sessions, not just the one that triggered the change. The
|
|
452
|
+
SPA's session lives entirely in a JWT access token (in-memory, 15-min TTL) plus
|
|
453
|
+
an httpOnly refresh cookie (#897), so revoking every *refresh* token is the
|
|
454
|
+
complete server-side session revocation: a blacklisted refresh token can no
|
|
455
|
+
longer be exchanged for a new access token, and the old access token self-
|
|
456
|
+
expires within its short TTL.
|
|
457
|
+
|
|
458
|
+
Django sessions are intentionally left untouched — the SPA never authenticates
|
|
459
|
+
via ``django.contrib.sessions`` (that framework backs only the admin site and
|
|
460
|
+
the DRF browsable API), so there is no app session to clear there.
|
|
461
|
+
|
|
462
|
+
Completeness depends on every live token having an ``OutstandingToken`` row.
|
|
463
|
+
simplejwt writes one only in ``RefreshToken.for_user`` (login); rotation mints a
|
|
464
|
+
new jti and records nothing, so before #2999 this function walked a set of
|
|
465
|
+
already-blacklisted rows and left the *live* session usable while returning a
|
|
466
|
+
plausible non-zero count. ``core.auth_views._record_outstanding`` now upserts the
|
|
467
|
+
row on both login and rotation, which is what makes this function's contract
|
|
468
|
+
true. **Tokens minted before that fix shipped still have no row and remain
|
|
469
|
+
unrevokable** — an operator needing a hard cut across those must rotate
|
|
470
|
+
``JWT_SIGNING_KEY``.
|
|
471
|
+
|
|
472
|
+
Idempotent: ``get_or_create`` means re-running blacklists nothing twice, and a
|
|
473
|
+
row already blacklisted by rotation stays blacklisted. Safe to call inside the
|
|
474
|
+
reset transaction.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
user: The account whose refresh tokens should all be revoked.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
The number of tokens newly blacklisted (already-blacklisted tokens are not
|
|
481
|
+
counted). Zero when the account had no outstanding tokens (e.g. it has
|
|
482
|
+
never logged in on this deployment).
|
|
483
|
+
"""
|
|
484
|
+
# Local import: the blacklist app's models are only importable once apps are
|
|
485
|
+
# loaded, and keeping the import lazy lets a lean deploy that removes the
|
|
486
|
+
# token_blacklist app degrade gracefully (the ImportError is swallowed → the
|
|
487
|
+
# password is still reset, sessions just fall back to TTL-only expiry).
|
|
488
|
+
try:
|
|
489
|
+
from rest_framework_simplejwt.token_blacklist.models import (
|
|
490
|
+
BlacklistedToken,
|
|
491
|
+
OutstandingToken,
|
|
492
|
+
)
|
|
493
|
+
except ImportError: # pragma: no cover - only when the app is uninstalled
|
|
494
|
+
return 0
|
|
495
|
+
|
|
496
|
+
revoked = 0
|
|
497
|
+
for token in OutstandingToken.objects.filter(user=user):
|
|
498
|
+
_, created = BlacklistedToken.objects.get_or_create(token=token)
|
|
499
|
+
if created:
|
|
500
|
+
revoked += 1
|
|
501
|
+
return revoked
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
def revoke_all_personal_access_tokens(
|
|
505
|
+
user: Any,
|
|
506
|
+
*,
|
|
507
|
+
actor: Any = None,
|
|
508
|
+
reason: str = "",
|
|
509
|
+
source_ip: str | None = None,
|
|
510
|
+
) -> int:
|
|
511
|
+
"""Soft-revoke every active Personal Access Token owned by ``user`` (ADR-0214).
|
|
512
|
+
|
|
513
|
+
Why this exists: a Personal Access Token is a *full-authority* bearer of the
|
|
514
|
+
user's own credentials — a script authenticating with it acts exactly as the
|
|
515
|
+
user. A password change is the user asserting "my credentials may be
|
|
516
|
+
compromised; cut everything off," so it must invalidate not only live sessions
|
|
517
|
+
(``revoke_all_refresh_tokens``) but also every long-lived personal credential
|
|
518
|
+
they minted. Leaving PATs live after a reset would defeat the whole point of
|
|
519
|
+
the reset — an attacker who phished the old password could keep the account via
|
|
520
|
+
a PAT they created. This is the PAT analogue of the refresh-token revocation and
|
|
521
|
+
is called immediately after it in the same atomic block.
|
|
522
|
+
|
|
523
|
+
Scope discipline: only ``owner=user`` (personal) tokens are revoked. Project-
|
|
524
|
+
and program-scoped tokens are *org assets* minted by an Admin/PM — they are not
|
|
525
|
+
the user's personal credentials and are deliberately left untouched, so a
|
|
526
|
+
password reset never breaks a team's CI integration.
|
|
527
|
+
|
|
528
|
+
Idempotent: already-revoked tokens are filtered out, so re-running is a no-op.
|
|
529
|
+
Uses a bulk ``update()`` (not per-row ``save()``) because revocation is a
|
|
530
|
+
single indexed write with no ``server_version`` sync semantics on the audit
|
|
531
|
+
path — the tokens are not mobile-synced resources whose version consumers track.
|
|
532
|
+
|
|
533
|
+
**Every revoked token gets an** ``ApiTokenAuditEntry`` **row (#2878).** It did
|
|
534
|
+
not, and the trail was inverted: routine self-service revocation through
|
|
535
|
+
``MyApiTokenViewSet.destroy`` was logged, while the two *security-relevant*
|
|
536
|
+
revocations — password reset and off-boarding, the two that answer "was this
|
|
537
|
+
account contained, and when?" — wrote nothing, on a feature page that promises
|
|
538
|
+
"every mint and revoke is written to an append-only audit log." The rows are
|
|
539
|
+
``bulk_create``d from the ids the ``update()`` matched, so the cost stays two
|
|
540
|
+
queries regardless of token count and the audit is inside the caller's
|
|
541
|
+
transaction: it commits with the revocation or rolls back with it, never apart.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
user: The account whose personal access tokens should all be revoked.
|
|
545
|
+
actor: The user who caused the revocation, for the audit row. The account
|
|
546
|
+
owner for a password reset; the acting admin for an off-boarding; ``None``
|
|
547
|
+
for an operator running the management command, which has no Django user.
|
|
548
|
+
reason: Short machine-readable cause recorded in ``detail["reason"]``
|
|
549
|
+
(``password_reset``, ``offboarding``, ``operator_bulk_revoke``). It is what
|
|
550
|
+
makes the trail answerable — "revoked" alone cannot distinguish routine
|
|
551
|
+
rotation from incident response.
|
|
552
|
+
source_ip: Client IP when one is available (a request-driven path); ``None``
|
|
553
|
+
from a management command.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
The number of tokens newly revoked (already-revoked tokens are excluded).
|
|
557
|
+
"""
|
|
558
|
+
# Local import: the projects app imports from access, so importing ApiToken at
|
|
559
|
+
# module top would create a circular dependency through the access → projects
|
|
560
|
+
# path (mirrors delete_program_cascade's lazy Project import).
|
|
561
|
+
from trueppm_api.apps.projects.models import (
|
|
562
|
+
ApiToken,
|
|
563
|
+
ApiTokenAuditAction,
|
|
564
|
+
ApiTokenAuditEntry,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
# Materialize before the update: after it, `revoked_at__isnull=True` no longer
|
|
568
|
+
# selects the rows this call is responsible for, and re-reading without the filter
|
|
569
|
+
# would also pick up tokens revoked earlier by someone else and double-audit them.
|
|
570
|
+
#
|
|
571
|
+
# `select_for_update()` is what keeps the split read/write as idempotent as the
|
|
572
|
+
# single `update()` it replaced. Without the row lock, a password reset racing an
|
|
573
|
+
# admin off-boarding both select the same rows, both write, and the second write
|
|
574
|
+
# moves `revoked_at` *forward* — misdating containment on the one field an auditor
|
|
575
|
+
# reads — while producing a second REVOKED row with a contradicting reason and an
|
|
576
|
+
# inflated return count. Holding the lock makes `doomed` exactly the set this call
|
|
577
|
+
# revokes, so the audit rows below cannot describe someone else's revocation. It
|
|
578
|
+
# requires an open transaction, which every caller has (ATOMIC_REQUESTS for the two
|
|
579
|
+
# request paths, an explicit `atomic()` in the management command); a caller without
|
|
580
|
+
# one gets a loud TransactionManagementError rather than a silent race.
|
|
581
|
+
#
|
|
582
|
+
# Expired tokens are excluded deliberately. An expired token already fails
|
|
583
|
+
# authentication (expiry is folded into the authenticator's hash lookup), so
|
|
584
|
+
# revoking it changes nothing — but auditing it would pad the trail with rows
|
|
585
|
+
# claiming N live credentials were contained when the real number was lower. This
|
|
586
|
+
# matches `ApiToken.active_personal_tokens_for`, the canonical definition of
|
|
587
|
+
# "active", which the 10-token cap already uses.
|
|
588
|
+
now = timezone.now()
|
|
589
|
+
doomed = list(
|
|
590
|
+
ApiToken.objects.select_for_update()
|
|
591
|
+
.filter(
|
|
592
|
+
models.Q(expires_at__isnull=True) | models.Q(expires_at__gt=now),
|
|
593
|
+
owner=user,
|
|
594
|
+
is_deleted=False,
|
|
595
|
+
revoked_at__isnull=True,
|
|
596
|
+
)
|
|
597
|
+
.only("pk", "token_prefix", "name")
|
|
598
|
+
)
|
|
599
|
+
if not doomed:
|
|
600
|
+
return 0
|
|
601
|
+
|
|
602
|
+
revoked = ApiToken.objects.filter(
|
|
603
|
+
pk__in=[token.pk for token in doomed], revoked_at__isnull=True
|
|
604
|
+
).update(revoked_at=now)
|
|
605
|
+
ApiTokenAuditEntry.objects.bulk_create(
|
|
606
|
+
[
|
|
607
|
+
ApiTokenAuditEntry(
|
|
608
|
+
owner=user,
|
|
609
|
+
token=token,
|
|
610
|
+
token_prefix=token.token_prefix,
|
|
611
|
+
actor=actor,
|
|
612
|
+
action=ApiTokenAuditAction.REVOKED.value,
|
|
613
|
+
source_ip=source_ip,
|
|
614
|
+
detail={"name": token.name, "reason": reason} if reason else {"name": token.name},
|
|
615
|
+
)
|
|
616
|
+
for token in doomed
|
|
617
|
+
]
|
|
618
|
+
)
|
|
619
|
+
return revoked
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def revoke_personal_durable_grants(user: Any = None, *, actor: Any = None) -> tuple[int, int]:
|
|
623
|
+
"""Revoke the two non-token durable grants a PAT (or a live session) can mint (#2939).
|
|
624
|
+
|
|
625
|
+
``IsNotTokenAuthenticated`` (#2878) now blocks *minting* a ``ShareLink`` or
|
|
626
|
+
rotating a ``BoardAutomation`` webhook secret through a bearer token, but a
|
|
627
|
+
grant minted **before** that guard existed, or minted through a live session by
|
|
628
|
+
someone who is now being contained, is exactly as durable as a personal access
|
|
629
|
+
token and sits outside every existing revocation lever
|
|
630
|
+
(``revoke_all_personal_access_tokens``, password reset, off-boarding). This
|
|
631
|
+
closes that gap for the two grants #2939 scoped: a project's public share
|
|
632
|
+
links and its git-automation webhook secret.
|
|
633
|
+
|
|
634
|
+
``user=None`` means instance-wide — every active grant, not one person's —
|
|
635
|
+
which is what the ``revoke_api_tokens --all`` full-compromise lever needs;
|
|
636
|
+
a scoped ``user`` is the off-boarding / single-account case.
|
|
637
|
+
|
|
638
|
+
Revoking a share link is cheap for a team to recover from (an Admin mints a
|
|
639
|
+
new one); clearing a webhook secret degrades ``GitWebhookIngestView`` to its
|
|
640
|
+
existing "no secret set" 404 refusal (fail-closed by construction, not a new
|
|
641
|
+
code path) until an Admin rotates a fresh one. Both are lower-disruption than
|
|
642
|
+
the project-/program-scoped *API tokens* this module deliberately leaves alone
|
|
643
|
+
on off-boarding (see ``_revoke_offboarded_credentials``) — those are shared
|
|
644
|
+
integration credentials embedded in other people's scripts and CI, not a
|
|
645
|
+
single admin's config action.
|
|
646
|
+
|
|
647
|
+
Returns ``(share_links_revoked, automation_secrets_cleared)``.
|
|
648
|
+
"""
|
|
649
|
+
# Local import: projects/integrations import from access, so importing these
|
|
650
|
+
# at module top would create a circular dependency (mirrors the ApiToken
|
|
651
|
+
# import above).
|
|
652
|
+
from trueppm_api.apps.integrations.models import BoardAutomation
|
|
653
|
+
from trueppm_api.apps.projects.models import ShareLink
|
|
654
|
+
|
|
655
|
+
now = timezone.now()
|
|
656
|
+
|
|
657
|
+
links = ShareLink.objects.filter(revoked_at__isnull=True)
|
|
658
|
+
if user is not None:
|
|
659
|
+
links = links.filter(created_by=user)
|
|
660
|
+
links_revoked = links.update(revoked_at=now, revoked_by=actor)
|
|
661
|
+
|
|
662
|
+
secrets_qs = BoardAutomation.objects.exclude(secret_ciphertext=b"")
|
|
663
|
+
if user is not None:
|
|
664
|
+
secrets_qs = secrets_qs.filter(configured_by=user)
|
|
665
|
+
secrets_cleared = secrets_qs.update(secret_ciphertext=b"", secret_set_at=None)
|
|
666
|
+
|
|
667
|
+
return links_revoked, secrets_cleared
|