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,405 @@
|
|
|
1
|
+
"""Seed a synthetic program at a chosen scale for capacity testing (issue #2391).
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
export TRUEPPM_CAPACITY_PASSWORD=... # required; see _resolve_password
|
|
6
|
+
python manage.py seed_capacity --projects 1 --tasks 4000 --edge-ratio 1.2
|
|
7
|
+
python manage.py seed_capacity --reset
|
|
8
|
+
|
|
9
|
+
Distinct from ``load_sample_project``, which loads a
|
|
10
|
+
*fixed-size* curated fixture for evaluation. This command generates an
|
|
11
|
+
arbitrarily large, structurally realistic program so the published scale
|
|
12
|
+
envelope can be measured to first sustained breach rather than to a fixed load.
|
|
13
|
+
|
|
14
|
+
**Not a demo seed.** The rows are synthetic filler; names are generated. Its only
|
|
15
|
+
job is to put a defensible shape of data behind the API at a known size.
|
|
16
|
+
|
|
17
|
+
Fidelity caveats — these are stated in the published envelope, not hidden here:
|
|
18
|
+
|
|
19
|
+
* Rows are written with ``bulk_create``, so per-row ``save()`` side effects are
|
|
20
|
+
skipped: no ``django-simple-history`` rows, and ``server_version`` stays 0.
|
|
21
|
+
A schedule read does not touch either, so read latency is unaffected — but
|
|
22
|
+
on-disk size and any sync-delta measurement will understate an organically
|
|
23
|
+
grown database.
|
|
24
|
+
* Dependencies are generated strictly forward (a lower task index always
|
|
25
|
+
precedes a higher one), which is acyclic by construction. Real programs
|
|
26
|
+
contain the same edge types but a messier topology.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
import random
|
|
33
|
+
from datetime import date
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from django.contrib.auth import get_user_model
|
|
37
|
+
from django.contrib.auth.password_validation import validate_password
|
|
38
|
+
from django.core.exceptions import ValidationError
|
|
39
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
40
|
+
from django.db import transaction
|
|
41
|
+
|
|
42
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
43
|
+
from trueppm_api.apps.projects.models import (
|
|
44
|
+
Dependency,
|
|
45
|
+
Program,
|
|
46
|
+
Project,
|
|
47
|
+
Task,
|
|
48
|
+
bulk_create_tasks,
|
|
49
|
+
)
|
|
50
|
+
from trueppm_api.apps.sync.sequence import allocate_for_projects
|
|
51
|
+
|
|
52
|
+
User = get_user_model()
|
|
53
|
+
|
|
54
|
+
CAPACITY_PROGRAM_CODE = "CAPACITY"
|
|
55
|
+
CAPACITY_OWNER_EMAIL = "capacity@trueppm.local"
|
|
56
|
+
|
|
57
|
+
# The owner is a real, loginable account, so its password is supplied from the
|
|
58
|
+
# environment rather than committed — a fixed credential in a tracked file is a
|
|
59
|
+
# secret-scanner finding however disposable the stack is (#2457). Same reasoning
|
|
60
|
+
# and same `${VAR:?}` compose pattern already used for CAPACITY_INTEGRATION_KEY.
|
|
61
|
+
CAPACITY_OWNER_PASSWORD_ENV = "TRUEPPM_CAPACITY_PASSWORD"
|
|
62
|
+
|
|
63
|
+
MINT_HINT = (
|
|
64
|
+
f" export {CAPACITY_OWNER_PASSWORD_ENV}=$(python3 -c "
|
|
65
|
+
"'import secrets; print(secrets.token_urlsafe(16))')"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Fixed seed so two runs of the same size produce the same graph — a capacity
|
|
69
|
+
# number that moves between runs must be the code changing, not the fixture.
|
|
70
|
+
RANDOM_SEED = 23910
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Command(BaseCommand):
|
|
74
|
+
help = "Generate a synthetic program at a chosen scale for capacity testing."
|
|
75
|
+
|
|
76
|
+
def add_arguments(self, parser: Any) -> None:
|
|
77
|
+
parser.add_argument("--projects", type=int, default=1, help="Projects to create.")
|
|
78
|
+
parser.add_argument(
|
|
79
|
+
"--tasks", type=int, default=1000, help="Tasks per project (not the total)."
|
|
80
|
+
)
|
|
81
|
+
parser.add_argument(
|
|
82
|
+
"--edge-ratio",
|
|
83
|
+
type=float,
|
|
84
|
+
default=1.0,
|
|
85
|
+
help=(
|
|
86
|
+
"Dependency edges per task. 1.0 is a single chain; above 1.0 adds "
|
|
87
|
+
"forward cross-links. CPM cost is edge-driven, so this is the "
|
|
88
|
+
"dimension that moves recompute time."
|
|
89
|
+
),
|
|
90
|
+
)
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"--breadth",
|
|
93
|
+
type=int,
|
|
94
|
+
default=12,
|
|
95
|
+
help="Children per WBS summary row — sets how deep the hierarchy runs.",
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"--reset",
|
|
99
|
+
action="store_true",
|
|
100
|
+
help="Delete the existing capacity program first (leaves other data alone).",
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument(
|
|
103
|
+
"--member-email",
|
|
104
|
+
default=None,
|
|
105
|
+
help=(
|
|
106
|
+
"Also grant an EXISTING user OWNER membership on every created "
|
|
107
|
+
"project. Lets a harness that authenticates as some other seeded "
|
|
108
|
+
"account read the capacity fixture."
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
|
113
|
+
projects: int = options["projects"]
|
|
114
|
+
tasks_per_project: int = options["tasks"]
|
|
115
|
+
edge_ratio: float = options["edge_ratio"]
|
|
116
|
+
breadth: int = options["breadth"]
|
|
117
|
+
member_email: str | None = options["member_email"]
|
|
118
|
+
|
|
119
|
+
if tasks_per_project < 1 or projects < 1:
|
|
120
|
+
raise CommandError("--projects and --tasks must both be >= 1.")
|
|
121
|
+
if edge_ratio < 0:
|
|
122
|
+
raise CommandError("--edge-ratio must be >= 0.")
|
|
123
|
+
|
|
124
|
+
rng = random.Random(RANDOM_SEED)
|
|
125
|
+
owner = self._resolve_owner()
|
|
126
|
+
# Resolved BEFORE any writing starts: a typo here must not leave a seeded
|
|
127
|
+
# program the harness cannot read (see _resolve_member).
|
|
128
|
+
member = self._resolve_member(member_email)
|
|
129
|
+
|
|
130
|
+
if options["reset"]:
|
|
131
|
+
self._reset(owner)
|
|
132
|
+
|
|
133
|
+
program = self._build_program(owner)
|
|
134
|
+
|
|
135
|
+
total_tasks = 0
|
|
136
|
+
total_edges = 0
|
|
137
|
+
for index in range(projects):
|
|
138
|
+
created_tasks, created_edges = self._build_project(
|
|
139
|
+
program=program,
|
|
140
|
+
owner=owner,
|
|
141
|
+
member=member,
|
|
142
|
+
index=index,
|
|
143
|
+
task_count=tasks_per_project,
|
|
144
|
+
edge_ratio=edge_ratio,
|
|
145
|
+
breadth=breadth,
|
|
146
|
+
rng=rng,
|
|
147
|
+
)
|
|
148
|
+
total_tasks += created_tasks
|
|
149
|
+
total_edges += created_edges
|
|
150
|
+
self.stdout.write(
|
|
151
|
+
f" project {index + 1}/{projects}: {created_tasks} tasks, {created_edges} edges"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
self.stdout.write(
|
|
155
|
+
self.style.SUCCESS(
|
|
156
|
+
f"Seeded {projects} project(s), {total_tasks} tasks, "
|
|
157
|
+
f"{total_edges} dependency edges under program "
|
|
158
|
+
f"{program.code} ({program.id})."
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
if member is not None:
|
|
162
|
+
self.stdout.write(f" also granted OWNER to {member.email}")
|
|
163
|
+
|
|
164
|
+
# ──────────────────────────────────────────────────────────────────
|
|
165
|
+
# Build steps
|
|
166
|
+
# ──────────────────────────────────────────────────────────────────
|
|
167
|
+
|
|
168
|
+
def _resolve_password(self) -> str:
|
|
169
|
+
"""The load driver's password, from the environment or not at all.
|
|
170
|
+
|
|
171
|
+
There is deliberately no default. The alternative — a committed constant
|
|
172
|
+
shared with ``run_capacity.py`` so both sides agree — is what this
|
|
173
|
+
replaces: a fixed password for a real, loginable account is a finding in
|
|
174
|
+
a public tree regardless of how disposable the stack it runs against is
|
|
175
|
+
(#2457). Failing loudly here is also the only way the operator learns the
|
|
176
|
+
runner will fail the same way three steps later.
|
|
177
|
+
"""
|
|
178
|
+
password = os.environ.get(CAPACITY_OWNER_PASSWORD_ENV, "").strip()
|
|
179
|
+
if not password:
|
|
180
|
+
raise CommandError(
|
|
181
|
+
f"{CAPACITY_OWNER_PASSWORD_ENV} is not set — the capacity owner's "
|
|
182
|
+
"password is supplied from the environment, never committed. "
|
|
183
|
+
f"Mint a throwaway one:\n{MINT_HINT}\n"
|
|
184
|
+
"See packages/api/perf/capacity/README.md."
|
|
185
|
+
)
|
|
186
|
+
return password
|
|
187
|
+
|
|
188
|
+
def _resolve_owner(self) -> Any:
|
|
189
|
+
"""The account the load driver authenticates as.
|
|
190
|
+
|
|
191
|
+
This command only ever runs against a disposable local capacity stack
|
|
192
|
+
that is torn down with ``down -v``. It must never be pointed at a real
|
|
193
|
+
deployment — hence the dedicated ``capacity@trueppm.local`` identity
|
|
194
|
+
rather than reusing an existing superuser.
|
|
195
|
+
"""
|
|
196
|
+
password = self._resolve_password()
|
|
197
|
+
# Held to the configured validators like any other credential, on both
|
|
198
|
+
# paths: an operator-supplied value is arbitrary, so a weak one must fail
|
|
199
|
+
# here rather than quietly standing up a guessable login.
|
|
200
|
+
owner = User.objects.filter(email=CAPACITY_OWNER_EMAIL).first()
|
|
201
|
+
try:
|
|
202
|
+
validate_password(password, user=owner)
|
|
203
|
+
except ValidationError as exc:
|
|
204
|
+
raise CommandError(
|
|
205
|
+
f"The value of {CAPACITY_OWNER_PASSWORD_ENV} does not clear this "
|
|
206
|
+
f"project's password validators: {'; '.join(exc.messages)}\n"
|
|
207
|
+
f"Mint one that does:\n{MINT_HINT}"
|
|
208
|
+
) from exc
|
|
209
|
+
if owner is None:
|
|
210
|
+
owner = User.objects.create_user(
|
|
211
|
+
username=CAPACITY_OWNER_EMAIL,
|
|
212
|
+
email=CAPACITY_OWNER_EMAIL,
|
|
213
|
+
password=password,
|
|
214
|
+
)
|
|
215
|
+
else:
|
|
216
|
+
owner.set_password(password)
|
|
217
|
+
owner.save(update_fields=["password"])
|
|
218
|
+
return owner
|
|
219
|
+
|
|
220
|
+
def _resolve_member(self, email: str | None) -> Any | None:
|
|
221
|
+
"""An EXISTING user to also grant membership to, or None.
|
|
222
|
+
|
|
223
|
+
Deliberately does not create the account. This flag exists so a harness
|
|
224
|
+
that authenticates as some *other* seeded identity can read the capacity
|
|
225
|
+
fixture — ``perf:load`` mints its JWT for ``seed_integration_fixtures``'
|
|
226
|
+
user and would otherwise get an empty task list, since project reads are
|
|
227
|
+
membership-scoped. Creating a missing user here would hand that harness a
|
|
228
|
+
brand-new account it has no password for, so a typo would surface as a
|
|
229
|
+
silently unreadable fixture — exactly the class of "the number measured
|
|
230
|
+
nothing" defect this seeding path was changed to fix (#2816). Fail instead.
|
|
231
|
+
"""
|
|
232
|
+
if email is None:
|
|
233
|
+
return None
|
|
234
|
+
member = User.objects.filter(email=email).first()
|
|
235
|
+
if member is None:
|
|
236
|
+
raise CommandError(
|
|
237
|
+
f"--member-email {email!r} does not match an existing user. This "
|
|
238
|
+
"flag grants membership to an account another seeder already "
|
|
239
|
+
"created; it never creates one. Seed that user first."
|
|
240
|
+
)
|
|
241
|
+
return member
|
|
242
|
+
|
|
243
|
+
def _reset(self, owner: Any) -> None:
|
|
244
|
+
"""Hard-delete the capacity program's *projects*, so a co-resident dev DB survives.
|
|
245
|
+
|
|
246
|
+
Deleting the Program alone is not enough and silently does the wrong
|
|
247
|
+
thing: ``Project.program`` is nullable, so the projects survive as
|
|
248
|
+
orphans and the next seed stacks on top of them — every subsequent
|
|
249
|
+
measurement then runs against more data than its label claims.
|
|
250
|
+
Memberships are cleared first because ``ProjectMembership.project`` is
|
|
251
|
+
``on_delete=PROTECT``; tasks and dependencies cascade from Project.
|
|
252
|
+
"""
|
|
253
|
+
program = Program.objects.filter(code=CAPACITY_PROGRAM_CODE).first()
|
|
254
|
+
if program is None:
|
|
255
|
+
self.stdout.write("reset: no prior capacity program")
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
project_ids = list(Project.objects.filter(program=program).values_list("pk", flat=True))
|
|
259
|
+
if not project_ids:
|
|
260
|
+
self.stdout.write("reset: capacity program had no projects")
|
|
261
|
+
return
|
|
262
|
+
|
|
263
|
+
ProjectMembership.objects.filter(project_id__in=project_ids).delete()
|
|
264
|
+
deleted, _ = Project.objects.filter(pk__in=project_ids).delete()
|
|
265
|
+
self.stdout.write(
|
|
266
|
+
f"reset: removed {len(project_ids)} prior capacity project(s) ({deleted} rows)"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# A reset that quietly under-deletes invalidates every number that
|
|
270
|
+
# follows, so prove it rather than assume it.
|
|
271
|
+
remaining = Project.objects.filter(program=program).count()
|
|
272
|
+
if remaining:
|
|
273
|
+
raise CommandError(f"reset failed: {remaining} capacity project(s) still present")
|
|
274
|
+
|
|
275
|
+
def _build_program(self, owner: Any) -> Program:
|
|
276
|
+
# ``Program.code`` is deliberately non-unique at the DB level (#2025), so
|
|
277
|
+
# this lookup cannot be constraint-backed.
|
|
278
|
+
# get-or-create-ok: local capacity harness, fixed constant code, single-writer CLI
|
|
279
|
+
program, _ = Program.objects.get_or_create(
|
|
280
|
+
code=CAPACITY_PROGRAM_CODE,
|
|
281
|
+
defaults={"name": "Capacity envelope"},
|
|
282
|
+
)
|
|
283
|
+
return program
|
|
284
|
+
|
|
285
|
+
@transaction.atomic
|
|
286
|
+
def _build_project(
|
|
287
|
+
self,
|
|
288
|
+
*,
|
|
289
|
+
program: Program,
|
|
290
|
+
owner: Any,
|
|
291
|
+
member: Any | None,
|
|
292
|
+
index: int,
|
|
293
|
+
task_count: int,
|
|
294
|
+
edge_ratio: float,
|
|
295
|
+
breadth: int,
|
|
296
|
+
rng: random.Random,
|
|
297
|
+
) -> tuple[int, int]:
|
|
298
|
+
project = Project.objects.create(
|
|
299
|
+
name=f"Capacity project {index + 1}",
|
|
300
|
+
start_date=date(2026, 1, 5),
|
|
301
|
+
program=program,
|
|
302
|
+
lead=owner,
|
|
303
|
+
code=f"CAP{index + 1:03d}",
|
|
304
|
+
)
|
|
305
|
+
# The load driver authenticates as this user, so the membership is what
|
|
306
|
+
# makes every measured read reach a 200 rather than a 403.
|
|
307
|
+
ProjectMembership.objects.get_or_create(
|
|
308
|
+
project=project, user=owner, defaults={"role": Role.OWNER}
|
|
309
|
+
)
|
|
310
|
+
# Same reason as the owner's membership above: without a row here the
|
|
311
|
+
# requesting identity's project-scoped reads return 403/empty.
|
|
312
|
+
if member is not None and member.pk != owner.pk:
|
|
313
|
+
ProjectMembership.objects.get_or_create(
|
|
314
|
+
project=project, user=member, defaults={"role": Role.OWNER}
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# bulk_create bypasses save(), so nothing draws a sync delta cursor for
|
|
318
|
+
# these rows — they would sit at sync_seq=0 and be invisible to the sync
|
|
319
|
+
# pull, which would quietly make a seeded project useless for measuring it
|
|
320
|
+
# (ADR-0686). One value for the whole seed is right: it is one bulk write.
|
|
321
|
+
seq = allocate_for_projects([project.pk])
|
|
322
|
+
|
|
323
|
+
tasks = self._make_tasks(project, task_count, breadth, rng)
|
|
324
|
+
for task in tasks:
|
|
325
|
+
task.sync_seq = seq
|
|
326
|
+
# Through the funnel so a WBS-shaped seed declares its containers like every
|
|
327
|
+
# other bulk writer (#3030). Nothing is declared here, and for a reason worth
|
|
328
|
+
# writing down: `_make_tasks` emits ONLY depth-3 leaf paths and never the "1"
|
|
329
|
+
# or "1.1" rows above them, so this project's tree has no parent rows at all.
|
|
330
|
+
# The ancestor sweep therefore reports every one of those missing levels as
|
|
331
|
+
# "outside the batch" and looks them up against nothing. That costs a handful
|
|
332
|
+
# of empty queries, and it means the load fixture can never exercise the
|
|
333
|
+
# declaration path it now routes through — but going around the funnel is what
|
|
334
|
+
# would make the next change to this shape silent.
|
|
335
|
+
bulk_create_tasks(tasks, batch_size=1000)
|
|
336
|
+
|
|
337
|
+
# bulk_create bypassed save(), which is what normally advances the
|
|
338
|
+
# per-project short_id counter — reconcile it so any task created through
|
|
339
|
+
# the API afterwards does not collide with a seeded short_id.
|
|
340
|
+
Project.objects.filter(pk=project.pk).update(object_sequence=task_count)
|
|
341
|
+
|
|
342
|
+
edges = self._make_edges(tasks, edge_ratio, rng)
|
|
343
|
+
for edge in edges:
|
|
344
|
+
edge.sync_seq = seq
|
|
345
|
+
Dependency.objects.bulk_create(edges, batch_size=1000, ignore_conflicts=True)
|
|
346
|
+
|
|
347
|
+
return len(tasks), len(edges)
|
|
348
|
+
|
|
349
|
+
def _make_tasks(
|
|
350
|
+
self, project: Project, count: int, breadth: int, rng: random.Random
|
|
351
|
+
) -> list[Task]:
|
|
352
|
+
"""Build a WBS-shaped task list: every `breadth`-th row is a summary parent."""
|
|
353
|
+
tasks: list[Task] = []
|
|
354
|
+
for i in range(count):
|
|
355
|
+
# "1.4.7" style ltree path — depth grows as the index climbs, so a
|
|
356
|
+
# large project has a genuinely deep tree rather than a flat list.
|
|
357
|
+
major = i // (breadth * breadth) + 1
|
|
358
|
+
minor = (i // breadth) % breadth + 1
|
|
359
|
+
leaf = i % breadth + 1
|
|
360
|
+
wbs = f"{major}.{minor}.{leaf}"
|
|
361
|
+
tasks.append(
|
|
362
|
+
Task(
|
|
363
|
+
project=project,
|
|
364
|
+
name=f"Capacity task {i + 1}",
|
|
365
|
+
short_id=f"{i + 1:08X}",
|
|
366
|
+
wbs_path=wbs,
|
|
367
|
+
duration=rng.randint(1, 15),
|
|
368
|
+
percent_complete=0.0,
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
return tasks
|
|
372
|
+
|
|
373
|
+
def _make_edges(
|
|
374
|
+
self, tasks: list[Task], edge_ratio: float, rng: random.Random
|
|
375
|
+
) -> list[Dependency]:
|
|
376
|
+
"""Forward-only edges: a chain, plus cross-links to reach the target ratio.
|
|
377
|
+
|
|
378
|
+
Forward-only (predecessor index < successor index) makes the graph acyclic
|
|
379
|
+
by construction, so the seed can never trip the engine's cycle detection.
|
|
380
|
+
"""
|
|
381
|
+
edges: list[Dependency] = []
|
|
382
|
+
if len(tasks) < 2:
|
|
383
|
+
return edges
|
|
384
|
+
|
|
385
|
+
# The spine: every task depends on the one before it.
|
|
386
|
+
for i in range(1, len(tasks)):
|
|
387
|
+
edges.append(Dependency(predecessor=tasks[i - 1], successor=tasks[i], dep_type="FS"))
|
|
388
|
+
|
|
389
|
+
target = int(len(tasks) * edge_ratio)
|
|
390
|
+
extra = max(0, target - len(edges))
|
|
391
|
+
for _ in range(extra):
|
|
392
|
+
successor_idx = rng.randrange(1, len(tasks))
|
|
393
|
+
# Reach back up to 50 rows, so cross-links stay local rather than
|
|
394
|
+
# spanning the whole project — closer to how real plans are wired.
|
|
395
|
+
predecessor_idx = max(0, successor_idx - rng.randint(2, 50))
|
|
396
|
+
if predecessor_idx >= successor_idx:
|
|
397
|
+
continue
|
|
398
|
+
edges.append(
|
|
399
|
+
Dependency(
|
|
400
|
+
predecessor=tasks[predecessor_idx],
|
|
401
|
+
successor=tasks[successor_idx],
|
|
402
|
+
dep_type=rng.choice(["FS", "SS", "FF"]),
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
return edges
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Seed minimal fixtures for the web:integration Playwright CI job.
|
|
2
|
+
|
|
3
|
+
Creates a deterministic user, project, membership, and one task so integration
|
|
4
|
+
tests have stable credentials and a real API surface to exercise.
|
|
5
|
+
|
|
6
|
+
Idempotent — re-running clears and re-seeds so CI always starts clean.
|
|
7
|
+
|
|
8
|
+
Environment variables:
|
|
9
|
+
INTEGRATION_USER_EMAIL default: ci@trueppm.test
|
|
10
|
+
INTEGRATION_USER_PASSWORD resolved password (see _resolve_integration_password)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import secrets
|
|
17
|
+
from datetime import date
|
|
18
|
+
|
|
19
|
+
from django.conf import settings
|
|
20
|
+
from django.contrib.auth import get_user_model
|
|
21
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
22
|
+
from django.db import transaction
|
|
23
|
+
|
|
24
|
+
_EMAIL = os.environ.get("INTEGRATION_USER_EMAIL", "ci@trueppm.test")
|
|
25
|
+
_MEMBER_EMAIL = "ci-member@trueppm.test"
|
|
26
|
+
_NONMEMBER_EMAIL = "ci-nonmember@trueppm.test"
|
|
27
|
+
_PROJECT_NAME = "CI Integration Project"
|
|
28
|
+
|
|
29
|
+
# Env var an operator (or CI) sets to choose a known fixture password. When
|
|
30
|
+
# unset, the password is the static default below under DEBUG (local-dev
|
|
31
|
+
# convenience) and a random token otherwise — never a fixed, guessable password
|
|
32
|
+
# on a public instance (#1375, residual of #1350). The CI job exports this
|
|
33
|
+
# variable, so the deterministic credential survives there without DEBUG.
|
|
34
|
+
INTEGRATION_PASSWORD_ENV = "INTEGRATION_USER_PASSWORD"
|
|
35
|
+
_DEBUG_DEFAULT_PASSWORD = "ci-integration-pw" # only honored under settings.DEBUG
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _resolve_integration_password() -> tuple[str, str]:
|
|
39
|
+
"""Resolve the integration fixture password and its source.
|
|
40
|
+
|
|
41
|
+
The seeded accounts are real, loginable users, so a fixed weak password must
|
|
42
|
+
never reach a public (non-DEBUG) instance (#1375). Returns
|
|
43
|
+
``(password, source)`` where ``source`` is one of ``"env"``, ``"debug"``, or
|
|
44
|
+
``"generated"`` — the caller uses it to decide whether the value is safe to
|
|
45
|
+
echo and whether a destructive re-seed should proceed. Resolution order:
|
|
46
|
+
|
|
47
|
+
1. ``INTEGRATION_USER_PASSWORD`` env var, if set — operator/CI opt-in to a
|
|
48
|
+
known value (the CI job exports it);
|
|
49
|
+
2. the static ``ci-integration-pw`` default when ``settings.DEBUG`` is on —
|
|
50
|
+
local-dev convenience, where the instance is not internet-reachable;
|
|
51
|
+
3. a random URL-safe token otherwise, printed once at seed time.
|
|
52
|
+
"""
|
|
53
|
+
env_password = os.environ.get(INTEGRATION_PASSWORD_ENV)
|
|
54
|
+
if env_password:
|
|
55
|
+
return env_password, "env"
|
|
56
|
+
if settings.DEBUG:
|
|
57
|
+
return _DEBUG_DEFAULT_PASSWORD, "debug"
|
|
58
|
+
return secrets.token_urlsafe(16), "generated"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Command(BaseCommand):
|
|
62
|
+
"""Seed integration-test user, project, and one task (idempotent)."""
|
|
63
|
+
|
|
64
|
+
help = "Seed minimal fixtures for the web:integration CI job."
|
|
65
|
+
|
|
66
|
+
def add_arguments(self, parser: object) -> None:
|
|
67
|
+
parser.add_argument( # type: ignore[attr-defined]
|
|
68
|
+
"--force",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help=(
|
|
71
|
+
"Allow the destructive re-seed to run even when a throwaway "
|
|
72
|
+
"password would be generated (DEBUG off and no env override)."
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
@transaction.atomic
|
|
77
|
+
def handle(self, *args: object, **options: object) -> None:
|
|
78
|
+
"""Create or reset the integration fixture set."""
|
|
79
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
80
|
+
from trueppm_api.apps.projects.models import Calendar, Project, Task
|
|
81
|
+
|
|
82
|
+
password, password_source = _resolve_integration_password()
|
|
83
|
+
# Prod guard (#1375): this command clears and re-seeds the fixture
|
|
84
|
+
# project — destructive. On a non-DEBUG instance with no env override the
|
|
85
|
+
# only resolvable password is a useless random throwaway, so refuse to
|
|
86
|
+
# wipe data unless the operator explicitly opts in with --force.
|
|
87
|
+
if password_source == "generated" and not options.get("force"):
|
|
88
|
+
raise CommandError(
|
|
89
|
+
"Refusing to seed integration fixtures on a non-DEBUG instance "
|
|
90
|
+
"without a password. This command clears and re-seeds the CI "
|
|
91
|
+
f"fixture project and would delete data. Set {INTEGRATION_PASSWORD_ENV} "
|
|
92
|
+
"(as the CI job does) or pass --force to override."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
User = get_user_model()
|
|
96
|
+
|
|
97
|
+
# The frontend login form posts {username: email} (the default Django
|
|
98
|
+
# User model has USERNAME_FIELD="username"), so the seeded user's
|
|
99
|
+
# username MUST equal its email — otherwise SimpleJWT returns 401 on
|
|
100
|
+
# every login attempt and every integration test fails at step one.
|
|
101
|
+
# get-or-create-ok: CI seed command, fixed constant fixture email — no duplicate reachable
|
|
102
|
+
user, _ = User.objects.update_or_create(
|
|
103
|
+
email=_EMAIL,
|
|
104
|
+
defaults={"username": _EMAIL},
|
|
105
|
+
)
|
|
106
|
+
# CI integration fixture: the password is resolved by
|
|
107
|
+
# _resolve_integration_password (env var > DEBUG default > random token),
|
|
108
|
+
# not an interactive-signup path — password validators do not apply (same
|
|
109
|
+
# for the two member fixtures below).
|
|
110
|
+
# nosemgrep: unvalidated-password
|
|
111
|
+
user.set_password(password)
|
|
112
|
+
user.save(update_fields=["password"])
|
|
113
|
+
|
|
114
|
+
# Idempotent project reset. Order matters: ProjectMembership.project is
|
|
115
|
+
# PROTECT (must clear memberships first), Task.project is CASCADE (auto-
|
|
116
|
+
# deletes with the project, but explicit delete is harmless), and
|
|
117
|
+
# Project.calendar is PROTECT (calendars must be deleted *after* their
|
|
118
|
+
# projects, not before — reversed order raises ProtectedError).
|
|
119
|
+
prior = Project.objects.filter(name=_PROJECT_NAME)
|
|
120
|
+
if prior.exists():
|
|
121
|
+
calendar_ids = list(prior.values_list("calendar_id", flat=True))
|
|
122
|
+
ProjectMembership.objects.filter(project__in=prior).delete()
|
|
123
|
+
Task.objects.filter(project__in=prior).delete()
|
|
124
|
+
prior.delete()
|
|
125
|
+
Calendar.objects.filter(id__in=[cid for cid in calendar_ids if cid]).delete()
|
|
126
|
+
|
|
127
|
+
cal = Calendar.objects.create(name=f"{_PROJECT_NAME} calendar")
|
|
128
|
+
project = Project.objects.create(
|
|
129
|
+
name=_PROJECT_NAME,
|
|
130
|
+
start_date=date.today(),
|
|
131
|
+
calendar=cal,
|
|
132
|
+
)
|
|
133
|
+
# OWNER so integration tests can exercise the full member-management UI
|
|
134
|
+
ProjectMembership.objects.create(project=project, user=user, role=Role.OWNER)
|
|
135
|
+
|
|
136
|
+
# Second user — project member; used by E2E to verify member list
|
|
137
|
+
# get-or-create-ok: CI seed command, fixed constant fixture email — no duplicate reachable
|
|
138
|
+
ci_member, _ = User.objects.update_or_create(
|
|
139
|
+
email=_MEMBER_EMAIL,
|
|
140
|
+
defaults={"username": _MEMBER_EMAIL},
|
|
141
|
+
)
|
|
142
|
+
# nosemgrep: unvalidated-password
|
|
143
|
+
ci_member.set_password(password)
|
|
144
|
+
ci_member.save(update_fields=["password"])
|
|
145
|
+
ProjectMembership.objects.create(project=project, user=ci_member, role=Role.MEMBER)
|
|
146
|
+
|
|
147
|
+
# Third user — has an account but no project membership; used by E2E
|
|
148
|
+
# member-search tests to find a user and add them to the project
|
|
149
|
+
# get-or-create-ok: CI seed command, fixed constant fixture email — no duplicate reachable
|
|
150
|
+
ci_nonmember, _ = User.objects.update_or_create(
|
|
151
|
+
email=_NONMEMBER_EMAIL,
|
|
152
|
+
defaults={"username": _NONMEMBER_EMAIL},
|
|
153
|
+
)
|
|
154
|
+
# nosemgrep: unvalidated-password
|
|
155
|
+
ci_nonmember.set_password(password)
|
|
156
|
+
ci_nonmember.save(update_fields=["password"])
|
|
157
|
+
|
|
158
|
+
# One seed task so the schedule view renders a non-empty state on first load.
|
|
159
|
+
Task.objects.create(
|
|
160
|
+
project=project,
|
|
161
|
+
name="CI Seed Task",
|
|
162
|
+
duration=1,
|
|
163
|
+
wbs_path="1",
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self.stdout.write(
|
|
167
|
+
self.style.SUCCESS(f"Integration fixtures seeded (project pk={project.pk})")
|
|
168
|
+
)
|
|
169
|
+
self.stdout.write(f" user: {_EMAIL}")
|
|
170
|
+
self.stdout.write(f" project: {_PROJECT_NAME!r}")
|
|
171
|
+
# Only echo the value when it cannot be recovered elsewhere (the generated
|
|
172
|
+
# random token) or is the well-known dev default. A password supplied via
|
|
173
|
+
# INTEGRATION_USER_PASSWORD is already in the operator's/CI secret store, so
|
|
174
|
+
# it is not re-emitted into stdout / container logs (#1375).
|
|
175
|
+
if password_source == "env":
|
|
176
|
+
self.stdout.write(f" password set via {INTEGRATION_PASSWORD_ENV}")
|
|
177
|
+
else:
|
|
178
|
+
self.stdout.write(f" password: {password!r}")
|