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,466 @@
|
|
|
1
|
+
"""Landing resolution — the role-based app front door (ADR-0129).
|
|
2
|
+
|
|
3
|
+
``resolve_landing(user)`` is a pure read that decides where a user lands on
|
|
4
|
+
login / on hitting ``/``. It is exposed as a server fact on ``/auth/me/`` so that
|
|
5
|
+
every client (web, mobile, MCP) resolves the *same* front door without
|
|
6
|
+
re-implementing the policy (API-first, CLAUDE.md).
|
|
7
|
+
|
|
8
|
+
The policy, in order:
|
|
9
|
+
|
|
10
|
+
1. An explicit ``default_landing`` preference wins (the escape hatch) — honored
|
|
11
|
+
*when reachable*; an unreachable preference (e.g. ``project_overview`` after
|
|
12
|
+
losing access to every project) falls through rather than producing a dead
|
|
13
|
+
route.
|
|
14
|
+
2. A user with no project/program membership lands on My Work (its onboarding
|
|
15
|
+
empty state) — never pushed into project creation.
|
|
16
|
+
3. Otherwise the role policy: PMO/Exec (portfolio-entitled, Enterprise) →
|
|
17
|
+
Portfolio; PM-type (``max_project_role >= SCHEDULER``) → most-recent project
|
|
18
|
+
Overview; contributor (MEMBER/VIEWER) → My Work.
|
|
19
|
+
|
|
20
|
+
The function fails *open*: any unexpected error resolves to My Work so the front
|
|
21
|
+
door can never 500.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from collections.abc import Callable
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from typing import Any, TypeVar
|
|
30
|
+
|
|
31
|
+
from django.conf import settings
|
|
32
|
+
from django.db.models import Max, QuerySet
|
|
33
|
+
|
|
34
|
+
from trueppm_api.apps.access.models import ProgramMembership, ProjectMembership, Role
|
|
35
|
+
from trueppm_api.apps.profiles.models import (
|
|
36
|
+
DateFormat,
|
|
37
|
+
DefaultLanding,
|
|
38
|
+
ProjectVisit,
|
|
39
|
+
RoleContext,
|
|
40
|
+
UserProfile,
|
|
41
|
+
)
|
|
42
|
+
from trueppm_api.core.extension_providers import guard_single_provider
|
|
43
|
+
|
|
44
|
+
logger = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
MY_WORK_PATH = "/me/work"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class Landing:
|
|
51
|
+
"""Resolved front door. ``intent`` is the stable semantic target (clients map
|
|
52
|
+
it to their own route); ``path`` is the concrete web route; ``resolved_by``
|
|
53
|
+
explains the decision for honest first-login / "why am I here?" affordances.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
intent: str # "my_work" | "project_overview" | "portfolio"
|
|
57
|
+
path: str
|
|
58
|
+
resolved_by: str # "preference" | "role_policy" | "fallback"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# --- Enterprise portfolio-access seam -------------------------------------
|
|
62
|
+
#
|
|
63
|
+
# OSS resolves ``portfolio`` to a real path only when the Enterprise overlay has
|
|
64
|
+
# registered a portfolio-access provider against this seam AND the running
|
|
65
|
+
# edition is Enterprise. The OSS core imports nothing from ``trueppm_enterprise``
|
|
66
|
+
# (the dependency is one-way, enterprise → core); absent a provider this returns
|
|
67
|
+
# False and the ``portfolio`` branch is never taken.
|
|
68
|
+
|
|
69
|
+
_portfolio_access_provider: Callable[[Any], bool] | None = None
|
|
70
|
+
_portfolio_path_provider: Callable[[Any], str] | None = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def register_portfolio_access_provider(
|
|
74
|
+
access: Callable[[Any], bool],
|
|
75
|
+
path: Callable[[Any], str],
|
|
76
|
+
) -> None:
|
|
77
|
+
"""Enterprise registration hook: supply (is-entitled, resolve-path) callables.
|
|
78
|
+
|
|
79
|
+
Called once at enterprise app-ready time. OSS never calls this, so
|
|
80
|
+
``has_portfolio_access`` stays False in the community edition.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
global _portfolio_access_provider, _portfolio_path_provider
|
|
84
|
+
_portfolio_access_provider = guard_single_provider(
|
|
85
|
+
_portfolio_access_provider, access, "portfolio access"
|
|
86
|
+
)
|
|
87
|
+
_portfolio_path_provider = guard_single_provider(
|
|
88
|
+
_portfolio_path_provider, path, "portfolio path"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def has_portfolio_access(user: Any) -> bool:
|
|
93
|
+
"""True only when Enterprise edition is running and a registered provider
|
|
94
|
+
grants this user portfolio entitlement. Always False in OSS."""
|
|
95
|
+
|
|
96
|
+
if settings.TRUEPPM_EDITION != "enterprise" or _portfolio_access_provider is None:
|
|
97
|
+
return False
|
|
98
|
+
try:
|
|
99
|
+
return bool(_portfolio_access_provider(user))
|
|
100
|
+
except Exception: # pragma: no cover - defensive: a bad provider must not 500 /me
|
|
101
|
+
logger.exception("portfolio access provider raised; treating as no access")
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _portfolio_path(user: Any) -> str | None:
|
|
106
|
+
if _portfolio_path_provider is None:
|
|
107
|
+
return None
|
|
108
|
+
try:
|
|
109
|
+
return _portfolio_path_provider(user)
|
|
110
|
+
except Exception: # pragma: no cover - defensive
|
|
111
|
+
logger.exception("portfolio path provider raised")
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# --- Helpers ---------------------------------------------------------------
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _overview_path(project_id: Any) -> str:
|
|
119
|
+
return f"/projects/{project_id}/overview"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def most_recent_project(user: Any) -> Any | None:
|
|
123
|
+
"""The user's most recently *visited* accessible project (ADR-0150).
|
|
124
|
+
|
|
125
|
+
Reads real last-visited telemetry from :class:`ProjectVisit` — the project
|
|
126
|
+
the user most recently opened, filtered to ones they still belong to and that
|
|
127
|
+
are not archived/deleted. One indexed query (``(user, -visited_at)``) on the
|
|
128
|
+
``/auth/me/`` hot path.
|
|
129
|
+
|
|
130
|
+
Falls back to ``_most_recent_project_proxy`` (the highest-``server_version``
|
|
131
|
+
membership, ADR-0129) when the user has no usable visit row yet — fresh users
|
|
132
|
+
and existing users on their first navigation after this shipped. Returns the
|
|
133
|
+
``Project`` or None.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
visit = (
|
|
137
|
+
ProjectVisit.objects.filter(
|
|
138
|
+
user=user,
|
|
139
|
+
project__is_deleted=False,
|
|
140
|
+
project__is_archived=False,
|
|
141
|
+
# Only count visits to projects the user still has access to — a
|
|
142
|
+
# membership may have been revoked since the visit was recorded.
|
|
143
|
+
project__memberships__user=user,
|
|
144
|
+
project__memberships__is_deleted=False,
|
|
145
|
+
)
|
|
146
|
+
.select_related("project")
|
|
147
|
+
.order_by("-visited_at")
|
|
148
|
+
.first()
|
|
149
|
+
)
|
|
150
|
+
if visit is not None:
|
|
151
|
+
return visit.project
|
|
152
|
+
return _most_recent_project_proxy(user)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def recent_projects(user: Any, limit: int = 5) -> list[Any]:
|
|
156
|
+
"""The user's most recently *visited* accessible projects (ADR-0150, ADR-0508).
|
|
157
|
+
|
|
158
|
+
The list form of :func:`most_recent_project`, backing the ⌘K "Recent" group
|
|
159
|
+
(#1557). Applies the *same* membership re-filter as the single-value resolver
|
|
160
|
+
so a project the user was removed from, or that was archived/deleted, never
|
|
161
|
+
surfaces from a stale visit row — the read is re-joined to live membership,
|
|
162
|
+
not trusted from the visit table alone (the 🔴 IDOR guard from the VoC panel).
|
|
163
|
+
|
|
164
|
+
One indexed query (``(user, -visited_at)``, ``projectvisit_user_recent_idx``)
|
|
165
|
+
with ``project``/``program`` joined in, sliced server-side. ``limit`` is
|
|
166
|
+
clamped to ``[1, 10]`` — this is a fixed navigation strip, not a search
|
|
167
|
+
surface. Returns the :class:`ProjectVisit` rows (each carries ``.project`` and
|
|
168
|
+
``.visited_at``) newest-first; unlike ``most_recent_project`` there is no
|
|
169
|
+
pre-telemetry proxy fallback — a user with no visit rows simply has an empty
|
|
170
|
+
Recent group.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
bounded = max(1, min(limit, 10))
|
|
174
|
+
return list(
|
|
175
|
+
ProjectVisit.objects.filter(
|
|
176
|
+
user=user,
|
|
177
|
+
project__is_deleted=False,
|
|
178
|
+
project__is_archived=False,
|
|
179
|
+
project__memberships__user=user,
|
|
180
|
+
project__memberships__is_deleted=False,
|
|
181
|
+
)
|
|
182
|
+
.select_related("project", "project__program")
|
|
183
|
+
.order_by("-visited_at")
|
|
184
|
+
.distinct()[:bounded]
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _most_recent_project_proxy(user: Any) -> Any | None:
|
|
189
|
+
"""Pre-telemetry fallback: highest-``server_version`` active membership.
|
|
190
|
+
|
|
191
|
+
The ADR-0129 proxy — a stand-in for "most recently touched" used only until
|
|
192
|
+
the user has a real :class:`ProjectVisit` row. Falls back to the
|
|
193
|
+
alphabetically-first accessible project.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
membership = (
|
|
197
|
+
ProjectMembership.objects.filter(user=user, is_deleted=False)
|
|
198
|
+
.select_related("project")
|
|
199
|
+
.filter(project__is_deleted=False, project__is_archived=False)
|
|
200
|
+
.order_by("-server_version", "project__name")
|
|
201
|
+
.first()
|
|
202
|
+
)
|
|
203
|
+
return membership.project if membership is not None else None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def record_project_visit(user: Any, project: Any) -> None:
|
|
207
|
+
"""Upsert the user's last-visited timestamp for ``project`` (ADR-0150).
|
|
208
|
+
|
|
209
|
+
Idempotent: keyed on the ``(user, project)`` unique constraint, so a repeat
|
|
210
|
+
call only advances ``visited_at`` (last-writer-wins, the desired semantic).
|
|
211
|
+
Best-effort — callers fire this fire-and-forget; it never raises into the
|
|
212
|
+
request path on a benign race.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
from django.db import IntegrityError
|
|
216
|
+
from django.utils import timezone
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
ProjectVisit.objects.update_or_create(
|
|
220
|
+
user=user,
|
|
221
|
+
project=project,
|
|
222
|
+
defaults={"visited_at": timezone.now()},
|
|
223
|
+
)
|
|
224
|
+
except IntegrityError: # pragma: no cover - concurrent first-write race; the row now exists
|
|
225
|
+
ProjectVisit.objects.filter(user=user, project=project).update(visited_at=timezone.now())
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _max_project_role(user: Any) -> int | None:
|
|
229
|
+
value = ProjectMembership.objects.filter(user=user, is_deleted=False).aggregate(
|
|
230
|
+
_max=Max("role")
|
|
231
|
+
)["_max"]
|
|
232
|
+
return int(value) if value is not None else None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _has_any_membership(user: Any) -> bool:
|
|
236
|
+
return (
|
|
237
|
+
ProjectMembership.objects.filter(user=user, is_deleted=False).exists()
|
|
238
|
+
or ProgramMembership.objects.filter(user=user, is_deleted=False).exists()
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# --- Public API ------------------------------------------------------------
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def get_default_landing(user: Any) -> str:
|
|
246
|
+
"""The user's stored ``default_landing`` preference (``"auto"`` if no row)."""
|
|
247
|
+
|
|
248
|
+
profile = UserProfile.objects.filter(user=user).only("default_landing").first()
|
|
249
|
+
return profile.default_landing if profile is not None else DefaultLanding.AUTO
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def get_hidden_views(user: Any) -> list[str]:
|
|
253
|
+
"""The user's stored ``hidden_views`` list (``[]`` if no row, ADR-0139)."""
|
|
254
|
+
|
|
255
|
+
profile = UserProfile.objects.filter(user=user).only("hidden_views").first()
|
|
256
|
+
return list(profile.hidden_views) if profile is not None else []
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def get_profile_prefs(user: Any) -> tuple[str, list[str], str, str, str]:
|
|
260
|
+
"""All app preferences in one row read — ``(default_landing, hidden_views,
|
|
261
|
+
role_context, timezone, date_format)``.
|
|
262
|
+
|
|
263
|
+
``/auth/me/`` surfaces all five fields, so reading them together (one
|
|
264
|
+
``.only()`` query) avoids extra ``UserProfile`` lookups per response. Returns
|
|
265
|
+
the defaults (``AUTO``, ``[]``, ``UNIFIED``, ``"auto"``, ``AUTO``) when the user
|
|
266
|
+
has no profile row yet — ``role_context`` defaults to the neutral dual-hat lens
|
|
267
|
+
(#412, ADR-0162), and ``timezone`` / ``date_format`` to the ``"auto"`` sentinel
|
|
268
|
+
(#1953, ADR-0410) that resolves client-side to the browser's zone/locale —
|
|
269
|
+
matching the model defaults so an unconfigured user reads as "Unified Today" with
|
|
270
|
+
zero-config browser-local display.
|
|
271
|
+
|
|
272
|
+
``schedule_in_deliver`` was the fourth element until ADR-0942 §3 / #3137 retired
|
|
273
|
+
the Schedule-in-Deliver placement preference: every view now has exactly one home
|
|
274
|
+
per render, so there is nothing left for the flag to place.
|
|
275
|
+
"""
|
|
276
|
+
|
|
277
|
+
profile = (
|
|
278
|
+
UserProfile.objects.filter(user=user)
|
|
279
|
+
.only(
|
|
280
|
+
"default_landing",
|
|
281
|
+
"hidden_views",
|
|
282
|
+
"role_context",
|
|
283
|
+
"timezone",
|
|
284
|
+
"date_format",
|
|
285
|
+
)
|
|
286
|
+
.first()
|
|
287
|
+
)
|
|
288
|
+
if profile is None:
|
|
289
|
+
return DefaultLanding.AUTO, [], RoleContext.UNIFIED, "auto", DateFormat.AUTO
|
|
290
|
+
return (
|
|
291
|
+
profile.default_landing,
|
|
292
|
+
list(profile.hidden_views),
|
|
293
|
+
profile.role_context,
|
|
294
|
+
profile.timezone,
|
|
295
|
+
profile.date_format,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
_UNSET: Any = object()
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def resolve_landing(user: Any, *, pref: Any = _UNSET, max_role: Any = _UNSET) -> Landing:
|
|
303
|
+
"""Resolve the front door for ``user``. Pure read; fails open to My Work.
|
|
304
|
+
|
|
305
|
+
``pref`` and ``max_role`` may be passed by a caller that has already computed
|
|
306
|
+
them (e.g. ``MeSerializer`` reads both for other fields on the same request)
|
|
307
|
+
so the resolver reuses them instead of re-querying — avoiding a duplicate
|
|
308
|
+
``UserProfile`` read and a duplicate ``Max(role)`` aggregate per ``/auth/me/``.
|
|
309
|
+
Omit them and the resolver computes them itself.
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
try:
|
|
313
|
+
return _resolve_landing(user, pref=pref, max_role=max_role)
|
|
314
|
+
except Exception: # defensive: the front door must never 500
|
|
315
|
+
logger.exception("resolve_landing failed for user %s; falling back to My Work", user.pk)
|
|
316
|
+
return Landing("my_work", MY_WORK_PATH, "fallback")
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _resolve_landing(user: Any, *, pref: Any = _UNSET, max_role: Any = _UNSET) -> Landing:
|
|
320
|
+
if pref is _UNSET:
|
|
321
|
+
pref = get_default_landing(user)
|
|
322
|
+
enterprise = settings.TRUEPPM_EDITION == "enterprise"
|
|
323
|
+
|
|
324
|
+
# 1. Explicit preference wins — honored when reachable, else falls through.
|
|
325
|
+
preferred = _landing_from_preference(user, pref, enterprise)
|
|
326
|
+
if preferred is not None:
|
|
327
|
+
return preferred
|
|
328
|
+
|
|
329
|
+
# 2. No memberships at all → onboarding via My Work's empty state.
|
|
330
|
+
if not _has_any_membership(user):
|
|
331
|
+
return Landing("my_work", MY_WORK_PATH, "fallback")
|
|
332
|
+
|
|
333
|
+
# 3. AUTO role policy.
|
|
334
|
+
return _landing_from_role_policy(user, enterprise, max_role)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _landing_from_preference(user: Any, pref: Any, enterprise: bool) -> Landing | None:
|
|
338
|
+
"""Landing for an explicit preference, or ``None`` to fall through to policy."""
|
|
339
|
+
if pref == DefaultLanding.MY_WORK:
|
|
340
|
+
return Landing("my_work", MY_WORK_PATH, "preference")
|
|
341
|
+
if pref == DefaultLanding.PROJECT_OVERVIEW:
|
|
342
|
+
proj = most_recent_project(user)
|
|
343
|
+
if proj is not None:
|
|
344
|
+
return Landing("project_overview", _overview_path(proj.pk), "preference")
|
|
345
|
+
# unreachable (no accessible project) → fall through to role policy
|
|
346
|
+
if pref == DefaultLanding.PORTFOLIO and enterprise and has_portfolio_access(user):
|
|
347
|
+
path = _portfolio_path(user)
|
|
348
|
+
if path is not None:
|
|
349
|
+
return Landing("portfolio", path, "preference")
|
|
350
|
+
# provider gave no path → fall through
|
|
351
|
+
return None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _landing_from_role_policy(user: Any, enterprise: bool, max_role: Any) -> Landing:
|
|
355
|
+
"""AUTO role policy: Portfolio (entitled) → PM Overview → My Work."""
|
|
356
|
+
# PMO / Exec → Portfolio when entitled (Enterprise), else continue.
|
|
357
|
+
if enterprise and has_portfolio_access(user):
|
|
358
|
+
path = _portfolio_path(user)
|
|
359
|
+
if path is not None:
|
|
360
|
+
return Landing("portfolio", path, "role_policy")
|
|
361
|
+
|
|
362
|
+
if max_role is _UNSET:
|
|
363
|
+
max_role = _max_project_role(user)
|
|
364
|
+
|
|
365
|
+
# PM-type (SCHEDULER+) → most-recent project Overview.
|
|
366
|
+
if max_role is not None and max_role >= Role.SCHEDULER:
|
|
367
|
+
proj = most_recent_project(user)
|
|
368
|
+
if proj is not None:
|
|
369
|
+
return Landing("project_overview", _overview_path(proj.pk), "role_policy")
|
|
370
|
+
return Landing("my_work", MY_WORK_PATH, "fallback")
|
|
371
|
+
|
|
372
|
+
# Contributor (MEMBER / VIEWER) → My Work.
|
|
373
|
+
return Landing("my_work", MY_WORK_PATH, "role_policy")
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# ---------------------------------------------------------------------------
|
|
377
|
+
# Per-user pins (#2390, ADR-0627)
|
|
378
|
+
# ---------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class PinLimitReached(Exception):
|
|
382
|
+
"""Raised when a pin would exceed ``TRUEPPM_MAX_USER_PINS`` for this user.
|
|
383
|
+
|
|
384
|
+
Carries the cap so the view can name the actual limit in its message rather
|
|
385
|
+
than hardcoding a number that an operator may have overridden.
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
def __init__(self, limit: int) -> None:
|
|
389
|
+
self.limit = limit
|
|
390
|
+
super().__init__(f"User has reached the maximum of {limit} pinned items.")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def set_pin(user: Any, *, project: Any = None, program: Any = None, pinned: bool) -> bool:
|
|
394
|
+
"""Pin or unpin one project *or* one program for ``user``.
|
|
395
|
+
|
|
396
|
+
Returns whether the pin exists after the call, so the caller can echo the
|
|
397
|
+
resulting state without a second query. Idempotent in both directions: a
|
|
398
|
+
repeated pin is a no-op that still reports ``True``, and unpinning something
|
|
399
|
+
that was never pinned reports ``False`` rather than 404 — a pin is a personal
|
|
400
|
+
preference, and making its absence an error would surface a race between two
|
|
401
|
+
of the user's own tabs as a failure.
|
|
402
|
+
|
|
403
|
+
The cap is checked only when *adding*, and only when the row does not already
|
|
404
|
+
exist, so a user sitting exactly at the limit can still unpin and re-pin.
|
|
405
|
+
|
|
406
|
+
Scoped entirely to ``user``; there is no code path here that reads or writes
|
|
407
|
+
another user's pins (ADR-0627 §D5).
|
|
408
|
+
"""
|
|
409
|
+
from django.conf import settings
|
|
410
|
+
|
|
411
|
+
from trueppm_api.apps.profiles.models import UserPin
|
|
412
|
+
|
|
413
|
+
if (project is None) == (program is None):
|
|
414
|
+
raise ValueError("set_pin requires exactly one of project= or program=")
|
|
415
|
+
|
|
416
|
+
target = {"project": project} if project is not None else {"program": program}
|
|
417
|
+
|
|
418
|
+
if not pinned:
|
|
419
|
+
UserPin.objects.filter(user=user, **target).delete()
|
|
420
|
+
return False
|
|
421
|
+
|
|
422
|
+
existing = UserPin.objects.filter(user=user, **target).first()
|
|
423
|
+
if existing is not None:
|
|
424
|
+
return True
|
|
425
|
+
|
|
426
|
+
limit = settings.TRUEPPM_MAX_USER_PINS
|
|
427
|
+
if UserPin.objects.filter(user=user).count() >= limit:
|
|
428
|
+
raise PinLimitReached(limit)
|
|
429
|
+
|
|
430
|
+
# get_or_create rather than create: two concurrent pins of the same target
|
|
431
|
+
# (double-click, or two tabs) would otherwise race past the `existing` check
|
|
432
|
+
# into an IntegrityError on the partial unique constraint.
|
|
433
|
+
UserPin.objects.get_or_create(user=user, **target)
|
|
434
|
+
return True
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
_QS = TypeVar("_QS", bound=QuerySet[Any, Any])
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def annotate_is_pinned(queryset: _QS, user: Any, *, field: str = "project") -> _QS:
|
|
441
|
+
"""Annotate ``is_pinned`` for ``user`` onto a Project or Program queryset.
|
|
442
|
+
|
|
443
|
+
The subquery is bound to ``user`` **positionally** — it is a closed-over
|
|
444
|
+
value, not a request parameter — which is the structural reason this
|
|
445
|
+
annotation can answer "did *I* pin this" and can never be coerced into
|
|
446
|
+
answering "did someone else pin this" (ADR-0627 §D5). Any future change that
|
|
447
|
+
threads a caller-supplied user id in here is a privacy regression and needs a
|
|
448
|
+
superseding ADR.
|
|
449
|
+
|
|
450
|
+
``field`` selects which FK on ``UserPin`` to match — ``"project"`` or
|
|
451
|
+
``"program"``. Both are covered by a partial unique index, so the Exists()
|
|
452
|
+
probe is an index lookup rather than a scan.
|
|
453
|
+
"""
|
|
454
|
+
from django.db.models import BooleanField, Exists, OuterRef, Value
|
|
455
|
+
|
|
456
|
+
from trueppm_api.apps.profiles.models import UserPin
|
|
457
|
+
|
|
458
|
+
if not getattr(user, "is_authenticated", False):
|
|
459
|
+
# Anonymous reads (public share links) never carry a pin state.
|
|
460
|
+
return queryset.annotate(is_pinned=Value(False, output_field=BooleanField()))
|
|
461
|
+
|
|
462
|
+
return queryset.annotate(
|
|
463
|
+
is_pinned=Exists(
|
|
464
|
+
UserPin.objects.filter(user=user, **{field: OuterRef("pk")}),
|
|
465
|
+
)
|
|
466
|
+
)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""Views for the profiles app (ADR-0129).
|
|
2
|
+
|
|
3
|
+
Exposes the caller's own app preferences at ``/auth/me/profile/``. There is no
|
|
4
|
+
``:id`` in the path and the view only ever touches ``request.user``'s row, so
|
|
5
|
+
there is no IDOR surface — a user can read and write only their own profile.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, cast
|
|
11
|
+
|
|
12
|
+
from django.contrib.auth.models import User
|
|
13
|
+
from drf_spectacular.utils import extend_schema
|
|
14
|
+
from rest_framework.permissions import IsAuthenticated
|
|
15
|
+
from rest_framework.request import Request
|
|
16
|
+
from rest_framework.response import Response
|
|
17
|
+
from rest_framework.views import APIView
|
|
18
|
+
|
|
19
|
+
from trueppm_api.apps.profiles.models import UserPin, UserProfile
|
|
20
|
+
from trueppm_api.apps.profiles.serializers import PinnedItemSerializer, UserProfileSerializer
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MyProfileView(APIView):
|
|
24
|
+
"""GET/PATCH ``/api/v1/auth/me/profile/`` — the caller's own app preferences.
|
|
25
|
+
|
|
26
|
+
The profile row is created lazily, so a user who has never set a preference
|
|
27
|
+
reads the default (``default_landing="auto"``) without a backfill.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
permission_classes = [IsAuthenticated]
|
|
31
|
+
|
|
32
|
+
def _get_profile(self, request: Request) -> UserProfile:
|
|
33
|
+
# IsAuthenticated guarantees a concrete user here; narrow off the
|
|
34
|
+
# User | AnonymousUser union for the FK lookup.
|
|
35
|
+
user = cast(User, request.user)
|
|
36
|
+
profile, _ = UserProfile.objects.get_or_create(user=user)
|
|
37
|
+
return profile
|
|
38
|
+
|
|
39
|
+
@extend_schema(responses={200: UserProfileSerializer})
|
|
40
|
+
def get(self, request: Request) -> Response:
|
|
41
|
+
return Response(UserProfileSerializer(self._get_profile(request)).data)
|
|
42
|
+
|
|
43
|
+
@extend_schema(request=UserProfileSerializer, responses={200: UserProfileSerializer})
|
|
44
|
+
def patch(self, request: Request) -> Response:
|
|
45
|
+
serializer = UserProfileSerializer(
|
|
46
|
+
self._get_profile(request), data=request.data, partial=True
|
|
47
|
+
)
|
|
48
|
+
serializer.is_valid(raise_exception=True)
|
|
49
|
+
serializer.save()
|
|
50
|
+
return Response(serializer.data)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MyPinnedView(APIView):
|
|
54
|
+
"""GET ``/api/v1/auth/me/pinned/`` — the caller's own pinned items (#2390).
|
|
55
|
+
|
|
56
|
+
Returns projects and programs in one merged, most-recently-pinned-first list
|
|
57
|
+
so the rail can render its single flat jump group without two round-trips.
|
|
58
|
+
|
|
59
|
+
**Self-scoped by construction.** The queryset filters on ``request.user`` and
|
|
60
|
+
takes no user parameter, so there is no shape of this request that returns
|
|
61
|
+
another person's pins (ADR-0627 §D5). ``pinned_at`` is exposed here and
|
|
62
|
+
*only* here — never on a shared Project/Program payload — because a recency
|
|
63
|
+
timestamp visible to a teammate is precisely what would let a pin be read as
|
|
64
|
+
an engagement metric.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
permission_classes = [IsAuthenticated]
|
|
68
|
+
|
|
69
|
+
@extend_schema(
|
|
70
|
+
summary="List the requesting user's pinned projects and programs",
|
|
71
|
+
description=(
|
|
72
|
+
"Returns **only the requesting user's own** pins, most recently "
|
|
73
|
+
"pinned first. There is no parameter that selects another user, and "
|
|
74
|
+
"no endpoint anywhere reports who pinned an item or how many users "
|
|
75
|
+
"pinned it. A pin is a private wayfinding preference."
|
|
76
|
+
),
|
|
77
|
+
responses={200: PinnedItemSerializer(many=True)},
|
|
78
|
+
)
|
|
79
|
+
def get(self, request: Request) -> Response:
|
|
80
|
+
user = cast(User, request.user)
|
|
81
|
+
pins = (
|
|
82
|
+
UserPin.objects.filter(user=user)
|
|
83
|
+
.select_related("project", "project__program", "program")
|
|
84
|
+
.order_by("-pinned_at")
|
|
85
|
+
)
|
|
86
|
+
items = [item for pin in pins if (item := _pinned_item(pin)) is not None]
|
|
87
|
+
return Response(PinnedItemSerializer(items, many=True).data)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _pinned_item(pin: UserPin) -> dict[str, Any] | None:
|
|
91
|
+
"""Flatten one UserPin row into the merged wire shape.
|
|
92
|
+
|
|
93
|
+
Projects carry their parent program's id/name so the rail can render the
|
|
94
|
+
"Program › Project" breadcrumb without a second lookup; programs leave both
|
|
95
|
+
null.
|
|
96
|
+
|
|
97
|
+
Returns ``None`` for a row with neither target. The XOR CheckConstraint makes
|
|
98
|
+
that unreachable, but the constraint is invisible to the type checker and a
|
|
99
|
+
silently skipped row is a better failure than a 500 on the whole rail.
|
|
100
|
+
"""
|
|
101
|
+
project = pin.project
|
|
102
|
+
if project is not None:
|
|
103
|
+
program = project.program
|
|
104
|
+
return {
|
|
105
|
+
"kind": "project",
|
|
106
|
+
"id": str(project.pk),
|
|
107
|
+
"name": project.name,
|
|
108
|
+
"code": None,
|
|
109
|
+
"program_id": str(program.pk) if program is not None else None,
|
|
110
|
+
"program_name": program.name if program is not None else None,
|
|
111
|
+
"pinned_at": pin.pinned_at,
|
|
112
|
+
}
|
|
113
|
+
owning_program = pin.program
|
|
114
|
+
if owning_program is None:
|
|
115
|
+
return None
|
|
116
|
+
return {
|
|
117
|
+
"kind": "program",
|
|
118
|
+
"id": str(owning_program.pk),
|
|
119
|
+
"name": owning_program.name,
|
|
120
|
+
"code": owning_program.code,
|
|
121
|
+
"program_id": None,
|
|
122
|
+
"program_name": None,
|
|
123
|
+
"pinned_at": pin.pinned_at,
|
|
124
|
+
}
|
|
File without changes
|