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,180 @@
|
|
|
1
|
+
"""Project → Program → Workspace working-calendar resolution (ADR-0441, #1987).
|
|
2
|
+
|
|
3
|
+
The *working calendar* (working-day mask, hours-per-day, timezone, holiday/shutdown
|
|
4
|
+
exceptions) that CPM uses can be set at three scopes. The *effective base calendar* a
|
|
5
|
+
project schedules against is resolved here, computed-on-read (ADR-0108) — there is no
|
|
6
|
+
stored/denormalized effective column to keep in sync. Clients (web, mobile, MCP) read
|
|
7
|
+
the serializer's ``effective_calendar`` / ``calendar_source``; they never re-implement
|
|
8
|
+
this precedence, and neither does the CPM seam (``scheduling.calendars`` calls
|
|
9
|
+
:func:`resolve_effective_base_calendar` to seed the composed mask).
|
|
10
|
+
|
|
11
|
+
Precedence (most specific wins):
|
|
12
|
+
|
|
13
|
+
project.calendar → program.calendar → workspace.calendar → system default
|
|
14
|
+
|
|
15
|
+
``None`` at every scope means "system default" — the code-level Mon-Fri/8h/UTC fallback
|
|
16
|
+
in ``build_sched_calendar(None)`` remains the single source of the system default; we do
|
|
17
|
+
**not** materialize a system-default ``Calendar`` row.
|
|
18
|
+
|
|
19
|
+
This is Shape A (NULL-means-inherit), mirroring ``iteration_label`` / ``sharing_settings``
|
|
20
|
+
/ ``attachment_policy``: each scope's column is nullable and ``NULL`` = inherit. The base
|
|
21
|
+
calendar it resolves still composes with the project's ``ProjectCalendarLayer`` overlays
|
|
22
|
+
(ADR-0251) — inheritance picks the base; overlays stack on top of it unchanged.
|
|
23
|
+
|
|
24
|
+
``ENFORCE`` (a workspace admin locking the calendar so a program/project cannot override
|
|
25
|
+
it) is an Enterprise capability. OSS ships the neutral hook below and registers no
|
|
26
|
+
provider, so ``ENFORCE`` degrades to ``SUGGEST`` (no lock). trueppm-enterprise registers a
|
|
27
|
+
provider in its ``AppConfig.ready()`` — the integrations-registry idiom (ADR-0029/0049),
|
|
28
|
+
mirroring ``iteration_label.register_terminology_enforcement_provider``. OSS never imports
|
|
29
|
+
enterprise.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from collections.abc import Callable
|
|
35
|
+
from typing import TYPE_CHECKING, Literal
|
|
36
|
+
|
|
37
|
+
from trueppm_api.core.extension_providers import guard_single_provider
|
|
38
|
+
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from trueppm_api.apps.projects.models import Calendar, Program, Project
|
|
41
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
42
|
+
|
|
43
|
+
#: Where the resolved base calendar came from. ``system_default`` means nothing up the
|
|
44
|
+
#: chain set a calendar, so CPM uses the Mon-Fri/8h/UTC backstop.
|
|
45
|
+
CalendarSource = Literal["project", "program", "workspace", "system_default"]
|
|
46
|
+
|
|
47
|
+
# Enterprise registers a zero-arg predicate that returns True when calendar-override
|
|
48
|
+
# enforcement is licensed/active. OSS leaves it None → enforcement inactive, so a
|
|
49
|
+
# workspace ENFORCE policy never locks downstream overrides in the community edition.
|
|
50
|
+
_ENFORCEMENT_PROVIDER: Callable[[], bool] | None = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def register_calendar_enforcement_provider(provider: Callable[[], bool] | None) -> None:
|
|
54
|
+
"""Register (or clear) the calendar-enforcement provider. Enterprise calls this."""
|
|
55
|
+
global _ENFORCEMENT_PROVIDER
|
|
56
|
+
_ENFORCEMENT_PROVIDER = guard_single_provider(
|
|
57
|
+
_ENFORCEMENT_PROVIDER, provider, "calendar enforcement"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def calendar_enforcement_active() -> bool:
|
|
62
|
+
"""True only when an enterprise provider is registered AND reports active.
|
|
63
|
+
|
|
64
|
+
OSS has no provider → always False, so a workspace ``ENFORCE`` policy never locks
|
|
65
|
+
downstream calendar overrides in the community edition.
|
|
66
|
+
"""
|
|
67
|
+
return _ENFORCEMENT_PROVIDER is not None and bool(_ENFORCEMENT_PROVIDER())
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def calendar_override_locked(workspace: Workspace) -> bool:
|
|
71
|
+
"""Whether per-scope calendar overrides are locked to the workspace calendar.
|
|
72
|
+
|
|
73
|
+
True under ``INHERIT`` (always — the affordance is hidden by design) or under
|
|
74
|
+
``ENFORCE`` *with* an active enterprise provider. ``SUGGEST`` (and OSS ``ENFORCE``
|
|
75
|
+
with no provider) returns False, so the per-scope override is honored and editable.
|
|
76
|
+
This is the single predicate the serializer and the write-permission gate both
|
|
77
|
+
consult, so resolution and enforcement can never drift apart.
|
|
78
|
+
"""
|
|
79
|
+
from trueppm_api.apps.workspace.models import TermOverridePolicy
|
|
80
|
+
|
|
81
|
+
policy = workspace.calendar_override_policy
|
|
82
|
+
if policy == TermOverridePolicy.INHERIT:
|
|
83
|
+
return True
|
|
84
|
+
if policy == TermOverridePolicy.ENFORCE:
|
|
85
|
+
return calendar_enforcement_active()
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _program_of(obj: Program | Project) -> Program | None:
|
|
90
|
+
"""The owning program of a project, or None for a program/standalone project."""
|
|
91
|
+
from trueppm_api.apps.projects.models import Program
|
|
92
|
+
|
|
93
|
+
if isinstance(obj, Program):
|
|
94
|
+
return None
|
|
95
|
+
return obj.program if obj.program_id else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def resolve_effective_base_calendar(
|
|
99
|
+
obj: Program | Project, *, workspace: Workspace | None = None
|
|
100
|
+
) -> Calendar | None:
|
|
101
|
+
"""Resolve the base ``Calendar`` a program or project schedules against.
|
|
102
|
+
|
|
103
|
+
Returns ``None`` when nothing up the chain sets a calendar — the caller then uses
|
|
104
|
+
the system default (Mon-Fri/8h/UTC). ``workspace`` may be passed to avoid re-loading
|
|
105
|
+
the singleton per object when resolving a list; otherwise it is loaded here.
|
|
106
|
+
"""
|
|
107
|
+
from trueppm_api.apps.projects.models import Program
|
|
108
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
109
|
+
|
|
110
|
+
if workspace is None:
|
|
111
|
+
workspace = Workspace.load()
|
|
112
|
+
|
|
113
|
+
if calendar_override_locked(workspace):
|
|
114
|
+
# INHERIT / active ENFORCE: the workspace calendar wins regardless of any
|
|
115
|
+
# lower-scope override.
|
|
116
|
+
return workspace.calendar
|
|
117
|
+
|
|
118
|
+
own = None if isinstance(obj, Program) else obj.calendar
|
|
119
|
+
program = _program_of(obj)
|
|
120
|
+
program_calendar = program.calendar if program else None
|
|
121
|
+
if own is not None:
|
|
122
|
+
return own
|
|
123
|
+
if isinstance(obj, Program):
|
|
124
|
+
return obj.calendar or workspace.calendar
|
|
125
|
+
return program_calendar or workspace.calendar
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def resolve_inherited_base_calendar(
|
|
129
|
+
obj: Program | Project, *, workspace: Workspace | None = None
|
|
130
|
+
) -> Calendar | None:
|
|
131
|
+
"""Base calendar ``obj`` would schedule against if its own override were cleared.
|
|
132
|
+
|
|
133
|
+
Drives the settings "Inherited from {scope}" affordance — it skips ``obj``'s own
|
|
134
|
+
value and resolves from the parent scope up. A Program's parent is the workspace; a
|
|
135
|
+
Project's parent is its program (whose own value resolves up to the workspace) or,
|
|
136
|
+
standalone, the workspace. Under an active lock the inherited value is the (mandatory)
|
|
137
|
+
workspace calendar.
|
|
138
|
+
"""
|
|
139
|
+
from trueppm_api.apps.projects.models import Program
|
|
140
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
141
|
+
|
|
142
|
+
if workspace is None:
|
|
143
|
+
workspace = Workspace.load()
|
|
144
|
+
|
|
145
|
+
if calendar_override_locked(workspace):
|
|
146
|
+
return workspace.calendar
|
|
147
|
+
|
|
148
|
+
if isinstance(obj, Program):
|
|
149
|
+
return workspace.calendar
|
|
150
|
+
program = _program_of(obj)
|
|
151
|
+
program_calendar = program.calendar if program else None
|
|
152
|
+
return program_calendar or workspace.calendar
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def resolve_calendar_source(
|
|
156
|
+
obj: Program | Project, *, workspace: Workspace | None = None
|
|
157
|
+
) -> CalendarSource:
|
|
158
|
+
"""Which scope supplied :func:`resolve_effective_base_calendar` for ``obj``.
|
|
159
|
+
|
|
160
|
+
The breadcrumb an integration or the settings UI reads to explain *why* a schedule
|
|
161
|
+
uses the calendar it does, without re-deriving the precedence (ADR-0441, VoC/Nadia).
|
|
162
|
+
"""
|
|
163
|
+
from trueppm_api.apps.projects.models import Program
|
|
164
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
165
|
+
|
|
166
|
+
if workspace is None:
|
|
167
|
+
workspace = Workspace.load()
|
|
168
|
+
|
|
169
|
+
locked = calendar_override_locked(workspace)
|
|
170
|
+
if not locked and not isinstance(obj, Program) and obj.calendar_id:
|
|
171
|
+
return "project"
|
|
172
|
+
if not locked and isinstance(obj, Program) and obj.calendar_id:
|
|
173
|
+
return "program"
|
|
174
|
+
if not locked:
|
|
175
|
+
program = _program_of(obj)
|
|
176
|
+
if program and program.calendar_id:
|
|
177
|
+
return "program"
|
|
178
|
+
if workspace.calendar_id:
|
|
179
|
+
return "workspace"
|
|
180
|
+
return "system_default"
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Views for Program Settings → Cadence & ceremonies (ADR-0079, issue #528).
|
|
2
|
+
|
|
3
|
+
CeremonyTemplate is a program-scoped CRUD resource; PhaseGateConfig is a
|
|
4
|
+
singleton (1:1) per program, exposed via a thin retrieve/update view that
|
|
5
|
+
lazy-creates the row on first GET.
|
|
6
|
+
|
|
7
|
+
Both surfaces are gated by program membership: VIEWER+ may read,
|
|
8
|
+
ADMIN+ may write. The 5-role matrix is enforced through the existing
|
|
9
|
+
``IsProgramMember`` and ``IsProgramAdmin`` permission classes — no new
|
|
10
|
+
permission infrastructure is introduced here.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from django.db import transaction
|
|
16
|
+
from django.db.models import QuerySet
|
|
17
|
+
from django.shortcuts import get_object_or_404
|
|
18
|
+
from rest_framework import mixins, status, viewsets
|
|
19
|
+
from rest_framework.exceptions import PermissionDenied
|
|
20
|
+
from rest_framework.generics import RetrieveUpdateAPIView
|
|
21
|
+
from rest_framework.permissions import BasePermission, IsAuthenticated
|
|
22
|
+
from rest_framework.request import Request
|
|
23
|
+
from rest_framework.response import Response
|
|
24
|
+
from rest_framework.serializers import BaseSerializer
|
|
25
|
+
|
|
26
|
+
from trueppm_api.apps.access.models import Role
|
|
27
|
+
from trueppm_api.apps.access.permissions import (
|
|
28
|
+
IsProgramAdmin,
|
|
29
|
+
IsProgramMember,
|
|
30
|
+
IsProgramNotClosed,
|
|
31
|
+
IsProjectMember,
|
|
32
|
+
IsProjectMemberWrite,
|
|
33
|
+
IsProjectNotArchived,
|
|
34
|
+
_membership_role,
|
|
35
|
+
)
|
|
36
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
37
|
+
from trueppm_api.apps.projects.models import (
|
|
38
|
+
COMPOSITION_GUARDRAIL_RULES,
|
|
39
|
+
CeremonyTemplate,
|
|
40
|
+
GuardrailLevel,
|
|
41
|
+
PhaseGateConfig,
|
|
42
|
+
Program,
|
|
43
|
+
Project,
|
|
44
|
+
ProjectGuardrailPolicy,
|
|
45
|
+
)
|
|
46
|
+
from trueppm_api.apps.projects.serializers import (
|
|
47
|
+
CeremonyTemplateSerializer,
|
|
48
|
+
PhaseGateConfigSerializer,
|
|
49
|
+
ProjectGuardrailPolicySerializer,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class CeremonyTemplateViewSet(
|
|
54
|
+
IdempotencyMixin,
|
|
55
|
+
mixins.ListModelMixin,
|
|
56
|
+
mixins.CreateModelMixin,
|
|
57
|
+
mixins.RetrieveModelMixin,
|
|
58
|
+
mixins.UpdateModelMixin,
|
|
59
|
+
mixins.DestroyModelMixin,
|
|
60
|
+
viewsets.GenericViewSet[CeremonyTemplate],
|
|
61
|
+
):
|
|
62
|
+
"""CRUD for program-level ceremony templates.
|
|
63
|
+
|
|
64
|
+
URL: ``/api/v1/programs/<program_pk>/ceremonies/[<pk>/]``
|
|
65
|
+
|
|
66
|
+
Permission matrix (ADR-0079):
|
|
67
|
+
list / retrieve — IsProgramMember (Viewer+)
|
|
68
|
+
create / update / destroy / enable toggle — IsProgramAdmin (Project Manager+)
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
serializer_class = CeremonyTemplateSerializer
|
|
72
|
+
|
|
73
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
74
|
+
if self.action in ("list", "retrieve"):
|
|
75
|
+
return [IsAuthenticated(), IsProgramMember(), IsProgramNotClosed()]
|
|
76
|
+
return [IsAuthenticated(), IsProgramAdmin(), IsProgramNotClosed()]
|
|
77
|
+
|
|
78
|
+
def get_queryset(self) -> QuerySet[CeremonyTemplate]:
|
|
79
|
+
# ``program_pk`` is guaranteed by the URL pattern; cast to str so the
|
|
80
|
+
# ORM lookup type-checks against the UUID/str union mypy expects.
|
|
81
|
+
program_pk = str(self.kwargs["program_pk"])
|
|
82
|
+
return (
|
|
83
|
+
CeremonyTemplate.objects.filter(
|
|
84
|
+
program_id=program_pk,
|
|
85
|
+
is_deleted=False,
|
|
86
|
+
)
|
|
87
|
+
.select_related("created_by")
|
|
88
|
+
.order_by("name")
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def perform_create(self, serializer: BaseSerializer[CeremonyTemplate]) -> None:
|
|
92
|
+
program_pk = self.kwargs["program_pk"]
|
|
93
|
+
program = get_object_or_404(Program, pk=program_pk, is_deleted=False)
|
|
94
|
+
# DRF skips ``has_object_permission`` on create — enforce the program
|
|
95
|
+
# admin gate against the resolved program before saving.
|
|
96
|
+
self.check_object_permissions(self.request, program)
|
|
97
|
+
serializer.save(program=program, created_by=self.request.user)
|
|
98
|
+
|
|
99
|
+
def perform_destroy(self, instance: CeremonyTemplate) -> None:
|
|
100
|
+
# Soft-delete so the sync protocol can ship a tombstone to clients
|
|
101
|
+
# rather than the row vanishing silently. The partial unique
|
|
102
|
+
# constraint on (program, name) excludes deleted rows so a name can
|
|
103
|
+
# be reused after deletion.
|
|
104
|
+
instance.soft_delete()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class PhaseGateConfigView(IdempotencyMixin, RetrieveUpdateAPIView[PhaseGateConfig]):
|
|
108
|
+
"""Singleton phase-gate calendar config per program.
|
|
109
|
+
|
|
110
|
+
URL: ``/api/v1/programs/<program_pk>/phase-gate-config/``
|
|
111
|
+
|
|
112
|
+
GET — IsProgramMember (Viewer+). Lazy-creates the row with defaults
|
|
113
|
+
(``enabled=False``, ``invite_template=""``) on first request so existing
|
|
114
|
+
programs do not require a data migration.
|
|
115
|
+
|
|
116
|
+
PATCH — IsProgramAdmin (Project Manager+).
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
serializer_class = PhaseGateConfigSerializer
|
|
120
|
+
http_method_names = ["get", "patch", "head", "options"] # noqa: RUF012
|
|
121
|
+
|
|
122
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
123
|
+
if self.request.method in ("GET", "HEAD", "OPTIONS"):
|
|
124
|
+
return [IsAuthenticated(), IsProgramMember(), IsProgramNotClosed()]
|
|
125
|
+
return [IsAuthenticated(), IsProgramAdmin(), IsProgramNotClosed()]
|
|
126
|
+
|
|
127
|
+
def get_object(self) -> PhaseGateConfig:
|
|
128
|
+
program_pk = self.kwargs["program_pk"]
|
|
129
|
+
program = get_object_or_404(Program, pk=program_pk, is_deleted=False)
|
|
130
|
+
# Membership / role enforcement uses the program as the target object
|
|
131
|
+
# for the configured permission classes (they read program_pk from
|
|
132
|
+
# ``self.kwargs`` via has_permission, then enforce on the object).
|
|
133
|
+
self.check_object_permissions(self.request, program)
|
|
134
|
+
config, _ = PhaseGateConfig.objects.get_or_create(program=program)
|
|
135
|
+
return config
|
|
136
|
+
|
|
137
|
+
def get_queryset(self) -> QuerySet[PhaseGateConfig]:
|
|
138
|
+
# Required by DRF's GenericAPIView contract even though we override
|
|
139
|
+
# get_object — the OpenAPI generator inspects this for schema output.
|
|
140
|
+
program_pk = str(self.kwargs["program_pk"])
|
|
141
|
+
return PhaseGateConfig.objects.filter(program_id=program_pk)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ProjectGuardrailPolicyView(IdempotencyMixin, RetrieveUpdateAPIView[ProjectGuardrailPolicy]):
|
|
145
|
+
"""Singleton sprint/phase/WBS guardrail policy per project (ADR-0101 §3).
|
|
146
|
+
|
|
147
|
+
URL: ``/api/v1/projects/<project_pk>/guardrail-policy/``
|
|
148
|
+
|
|
149
|
+
GET — IsProjectMember (Viewer+). Lazy-creates the row with default (all-WARN)
|
|
150
|
+
on first request so existing projects need no data migration.
|
|
151
|
+
|
|
152
|
+
PATCH — IsProjectMemberWrite (Team Member+): a Viewer must not be able to
|
|
153
|
+
weaken an existing guardrail (downgrade a BLOCK to WARN) or toggle
|
|
154
|
+
``acknowledged_by_team``. Escalating a *composition* rule to BLOCK
|
|
155
|
+
additionally requires ``role >= Role.OWNER`` (the sprint-sovereignty rule:
|
|
156
|
+
only the team's own Owner may turn a sprint-composition guardrail into a
|
|
157
|
+
wall). Lowering to WARN, and toggling ``acknowledged_by_team``, need only
|
|
158
|
+
member write — acknowledging an external policy is the team's call.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
serializer_class = ProjectGuardrailPolicySerializer
|
|
162
|
+
http_method_names = ["get", "patch", "head", "options"] # noqa: RUF012
|
|
163
|
+
|
|
164
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
165
|
+
# Safe methods stay Viewer+; unsafe methods (PATCH) require Team Member+
|
|
166
|
+
# so a Viewer cannot weaken a guardrail. The Owner-only escalation gate
|
|
167
|
+
# for composition BLOCK is enforced separately in ``perform_update``.
|
|
168
|
+
if self.request.method in ("GET", "HEAD", "OPTIONS"):
|
|
169
|
+
return [IsAuthenticated(), IsProjectMember(), IsProjectNotArchived()]
|
|
170
|
+
return [IsAuthenticated(), IsProjectMemberWrite(), IsProjectNotArchived()]
|
|
171
|
+
|
|
172
|
+
def get_object(self) -> ProjectGuardrailPolicy:
|
|
173
|
+
project_pk = self.kwargs["project_pk"]
|
|
174
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
175
|
+
self.check_object_permissions(self.request, project)
|
|
176
|
+
policy, _ = ProjectGuardrailPolicy.objects.get_or_create(project=project)
|
|
177
|
+
return policy
|
|
178
|
+
|
|
179
|
+
def get_queryset(self) -> QuerySet[ProjectGuardrailPolicy]:
|
|
180
|
+
project_pk = str(self.kwargs["project_pk"])
|
|
181
|
+
return ProjectGuardrailPolicy.objects.filter(project_id=project_pk)
|
|
182
|
+
|
|
183
|
+
def perform_update(self, serializer: BaseSerializer[ProjectGuardrailPolicy]) -> None:
|
|
184
|
+
"""Apply the level map with the sovereignty gate.
|
|
185
|
+
|
|
186
|
+
A request that newly sets any composition rule to BLOCK is rejected with
|
|
187
|
+
403 unless the caller is Owner+. The gate is checked against the *incoming*
|
|
188
|
+
levels, merged onto the existing map so a partial PATCH that touches one
|
|
189
|
+
rule doesn't silently clear the others.
|
|
190
|
+
|
|
191
|
+
Broadcasts ``guardrail_policy_updated`` on commit (#3773) so a second Owner
|
|
192
|
+
or Admin with Project Settings → Guardrails open concurrently sees the
|
|
193
|
+
change without a reload — the same sovereignty-gated path this view exists
|
|
194
|
+
for. The payload carries only the policy id, never the levels map: the
|
|
195
|
+
broadcast rides the project's board group, which every recipient already
|
|
196
|
+
had to be a project member to join, so there is no wider audience than the
|
|
197
|
+
GET this event tells them to re-issue — but keeping the payload
|
|
198
|
+
signal-only avoids relying on that as the only reason it's safe.
|
|
199
|
+
"""
|
|
200
|
+
instance = self.get_object()
|
|
201
|
+
validated = serializer.validated_data
|
|
202
|
+
incoming_levels = validated.get("levels")
|
|
203
|
+
if incoming_levels is not None:
|
|
204
|
+
escalates_composition_block = any(
|
|
205
|
+
rule in COMPOSITION_GUARDRAIL_RULES and level == GuardrailLevel.BLOCK
|
|
206
|
+
for rule, level in incoming_levels.items()
|
|
207
|
+
)
|
|
208
|
+
if escalates_composition_block:
|
|
209
|
+
role = _membership_role(self.request, self.kwargs["project_pk"])
|
|
210
|
+
if role is None or role < Role.OWNER:
|
|
211
|
+
raise PermissionDenied(
|
|
212
|
+
"Only the project Owner can set a sprint-composition guardrail to block."
|
|
213
|
+
)
|
|
214
|
+
# Merge onto the existing map so a partial update is non-destructive.
|
|
215
|
+
merged = dict(instance.levels)
|
|
216
|
+
merged.update(incoming_levels)
|
|
217
|
+
instance.levels = merged
|
|
218
|
+
if "acknowledged_by_team" in validated:
|
|
219
|
+
ack = validated["acknowledged_by_team"]
|
|
220
|
+
instance.acknowledged_by_team = ack
|
|
221
|
+
if ack and instance.acknowledged_at is None:
|
|
222
|
+
from django.utils import timezone
|
|
223
|
+
|
|
224
|
+
instance.acknowledged_at = timezone.now()
|
|
225
|
+
instance.save()
|
|
226
|
+
# Re-bind so the response reflects the persisted state.
|
|
227
|
+
serializer.instance = instance
|
|
228
|
+
|
|
229
|
+
# Snapshot to plain strings before the closure (broadcast-check H-1) —
|
|
230
|
+
# the view's mutable request/kwargs state must not be captured live.
|
|
231
|
+
project_id = str(self.kwargs["project_pk"])
|
|
232
|
+
policy_id = str(instance.id)
|
|
233
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
234
|
+
|
|
235
|
+
transaction.on_commit(
|
|
236
|
+
lambda: broadcast_board_event(project_id, "guardrail_policy_updated", {"id": policy_id})
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
def update(self, request: Request, *args: object, **kwargs: object) -> Response:
|
|
240
|
+
# RetrieveUpdateAPIView.update returns the serialized instance; our
|
|
241
|
+
# perform_update does the merge + gate. PATCH only (enforced by
|
|
242
|
+
# http_method_names), so this is always partial.
|
|
243
|
+
response = super().update(request, *args, **kwargs)
|
|
244
|
+
return Response(response.data, status=status.HTTP_200_OK)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""The commit moment: draft → active, and baseline v1 (#2963).
|
|
2
|
+
|
|
3
|
+
Nothing in the product marked *"this is the plan we agreed to."* A steering
|
|
4
|
+
committee could be shown today's plan but never *committed vs. actual*, because
|
|
5
|
+
there was no anchor to subtract from.
|
|
6
|
+
|
|
7
|
+
Committing is one transaction that does two things, and the design is explicit
|
|
8
|
+
that it is **not a lock**:
|
|
9
|
+
|
|
10
|
+
1. flips ``lifecycle`` draft → active, so the project joins the aggregates it was
|
|
11
|
+
excluded from (#2962);
|
|
12
|
+
2. captures **baseline v1 automatically**, freezing the working calendar it was
|
|
13
|
+
computed against (ADR-0845), superseding any baseline the draft already
|
|
14
|
+
carried.
|
|
15
|
+
|
|
16
|
+
Authoring continues afterwards. What changes is that the plan starts having a
|
|
17
|
+
past — a structural edit to committed work carries a reason from here on
|
|
18
|
+
(``amend.py``, #2964), which is live: :func:`amend.is_amendable` keys on
|
|
19
|
+
``lifecycle == ACTIVE`` and ``TaskViewSet.perform_update`` records the reason and
|
|
20
|
+
notifies the people whose work moved.
|
|
21
|
+
|
|
22
|
+
**Two acts this module does not perform, and did claim to.** This docstring
|
|
23
|
+
previously listed four, adding "commits the sprints in range" and "tells the
|
|
24
|
+
people who have work in it". Neither has ever had a code path here: no sprint is
|
|
25
|
+
read or written, and no notification row is created (contrast ``amend.notify_amend``,
|
|
26
|
+
which does exactly that on the *edit* side using the same ``TaskResource``
|
|
27
|
+
audience). They are removed rather than softened — a module docstring that
|
|
28
|
+
inventories a transaction's effects is read as the contract, and #3129 found the
|
|
29
|
+
permission rationale on ``ProjectCommitView`` had already been written on top of
|
|
30
|
+
the notification half of this fiction. Committing plans to tell the assigned team
|
|
31
|
+
is a real gap and belongs to a notifications issue, not to a docstring.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from dataclasses import dataclass
|
|
37
|
+
from typing import TYPE_CHECKING
|
|
38
|
+
|
|
39
|
+
from django.db import transaction
|
|
40
|
+
from django.db.models import DateField
|
|
41
|
+
from django.db.models.functions import Coalesce
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
# Unquoted in the signature below on purpose: `from __future__ import
|
|
45
|
+
# annotations` makes every annotation lazy, so this stays type-only — and a
|
|
46
|
+
# QUOTED annotation makes ruff think the import is unused and delete it,
|
|
47
|
+
# which it did once already.
|
|
48
|
+
from django.contrib.auth.models import User
|
|
49
|
+
|
|
50
|
+
from .models import (
|
|
51
|
+
Baseline,
|
|
52
|
+
BaselineTask,
|
|
53
|
+
Project,
|
|
54
|
+
ProjectLifecycle,
|
|
55
|
+
Task,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class AlreadyCommitted(Exception):
|
|
60
|
+
"""The project is already active.
|
|
61
|
+
|
|
62
|
+
Raised rather than silently re-committing: a second commit would capture a
|
|
63
|
+
*second* v1 and quietly move the anchor every variance number is measured
|
|
64
|
+
from, which is the one thing a baseline must never do.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class CommitResult:
|
|
70
|
+
baseline: Baseline
|
|
71
|
+
task_count: int
|
|
72
|
+
#: Everyone with a resource assignment in the plan. Named for what it is —
|
|
73
|
+
#: the audience a commit *concerns* — not for a delivery that does not
|
|
74
|
+
#: happen (#3129). See :func:`_assigned_resource_ids`.
|
|
75
|
+
assigned_resource_ids: list[str]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _calendar_snapshot(project: Project) -> dict[str, object]:
|
|
79
|
+
"""The working-calendar fields that affect date arithmetic (ADR-0845).
|
|
80
|
+
|
|
81
|
+
Snapshotted by value, never by reference. Calendars are editable, so storing
|
|
82
|
+
an id would let a later edit reshape a baseline that is supposed to be the
|
|
83
|
+
fixed side of every variance subtraction.
|
|
84
|
+
"""
|
|
85
|
+
calendar = project.calendar
|
|
86
|
+
if calendar is None:
|
|
87
|
+
return {}
|
|
88
|
+
return {
|
|
89
|
+
"calendar_id_at_capture": calendar.id,
|
|
90
|
+
"calendar_working_days": calendar.working_days,
|
|
91
|
+
"calendar_hours_per_day": calendar.hours_per_day,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@transaction.atomic
|
|
96
|
+
def commit_project(project: Project, *, user: User | None) -> CommitResult:
|
|
97
|
+
"""Take the project from draft to active, capturing baseline v1.
|
|
98
|
+
|
|
99
|
+
Atomic on purpose. A commit that flipped the lifecycle but failed to capture
|
|
100
|
+
the baseline would leave a project claiming to be committed with nothing to
|
|
101
|
+
measure against — worse than not committing at all, because the claim is
|
|
102
|
+
visible and the gap is not.
|
|
103
|
+
"""
|
|
104
|
+
# Re-read under the row lock: two clients pressing Commit at once must not
|
|
105
|
+
# both pass the check and capture two "v1"s.
|
|
106
|
+
locked = Project.objects.select_for_update().get(pk=project.pk)
|
|
107
|
+
if locked.lifecycle != ProjectLifecycle.DRAFT:
|
|
108
|
+
raise AlreadyCommitted(str(locked.pk))
|
|
109
|
+
|
|
110
|
+
live_tasks = list(
|
|
111
|
+
Task.objects.filter(project_id=locked.pk, is_deleted=False)
|
|
112
|
+
.annotate(_span_start=Coalesce("scheduled_start", "early_start", output_field=DateField()))
|
|
113
|
+
.values(
|
|
114
|
+
"id",
|
|
115
|
+
"name",
|
|
116
|
+
"early_start",
|
|
117
|
+
"early_finish",
|
|
118
|
+
"duration",
|
|
119
|
+
"actual_start",
|
|
120
|
+
"actual_finish",
|
|
121
|
+
"story_points",
|
|
122
|
+
"_span_start",
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
has_cpm_dates = bool(live_tasks) and all(t["early_start"] is not None for t in live_tasks)
|
|
126
|
+
|
|
127
|
+
# Stand down any incumbent active baseline before laying down v1 (#3129).
|
|
128
|
+
#
|
|
129
|
+
# A draft is fully writable, and `BaselineViewSet` is not lifecycle-gated, so
|
|
130
|
+
# a project can already carry an active baseline by the time anyone commits
|
|
131
|
+
# it. `Baseline` has a partial unique constraint on (project) WHERE is_active,
|
|
132
|
+
# so creating a second active row raised IntegrityError — a 500 on the only
|
|
133
|
+
# transition that can take a project out of draft at all (#3127 made
|
|
134
|
+
# `lifecycle` read-only), which is unrecoverable from the client.
|
|
135
|
+
#
|
|
136
|
+
# Deactivated, never deleted: the incumbent is somebody's captured snapshot
|
|
137
|
+
# and baseline history is kept forever. This is the same "deactivate the rest,
|
|
138
|
+
# then activate mine" sequence `BaselineActivateView` runs, and it is inside
|
|
139
|
+
# this function's `@transaction.atomic` so the two writes cannot interleave
|
|
140
|
+
# with a concurrent activate — the row lock taken above is on the project.
|
|
141
|
+
Baseline.objects.filter(project=locked, is_active=True).update(is_active=False)
|
|
142
|
+
|
|
143
|
+
baseline = Baseline.objects.create(
|
|
144
|
+
project=locked,
|
|
145
|
+
name="Baseline v1",
|
|
146
|
+
created_by=user if user is not None and user.is_authenticated else None,
|
|
147
|
+
is_active=True,
|
|
148
|
+
has_cpm_dates=has_cpm_dates,
|
|
149
|
+
**_calendar_snapshot(locked),
|
|
150
|
+
)
|
|
151
|
+
BaselineTask.objects.bulk_create(
|
|
152
|
+
[
|
|
153
|
+
BaselineTask(
|
|
154
|
+
baseline=baseline,
|
|
155
|
+
task_id=t["id"],
|
|
156
|
+
task_name=t["name"],
|
|
157
|
+
start=t["_span_start"],
|
|
158
|
+
finish=t["early_finish"],
|
|
159
|
+
duration=t["duration"],
|
|
160
|
+
actual_start=t["actual_start"],
|
|
161
|
+
actual_finish=t["actual_finish"],
|
|
162
|
+
story_points=t["story_points"],
|
|
163
|
+
)
|
|
164
|
+
for t in live_tasks
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
locked.lifecycle = ProjectLifecycle.ACTIVE
|
|
169
|
+
locked.save(update_fields=["lifecycle", "server_version"])
|
|
170
|
+
|
|
171
|
+
return CommitResult(
|
|
172
|
+
baseline=baseline,
|
|
173
|
+
task_count=len(live_tasks),
|
|
174
|
+
assigned_resource_ids=_assigned_resource_ids(locked),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _assigned_resource_ids(project: Project) -> list[str]:
|
|
179
|
+
"""Everyone with a resource assignment in the plan.
|
|
180
|
+
|
|
181
|
+
Deliberately ``TaskResource``, not ``Task.assignee``: capacity, utilization
|
|
182
|
+
and every sprint-capacity number sums ``TaskResource.units``, and a bare
|
|
183
|
+
assignee is zero load that may never reach the person at all. Addressing
|
|
184
|
+
``assignee`` would name the wrong set of people as having work in the plan.
|
|
185
|
+
|
|
186
|
+
This is the audience a commit *concerns*, and the caller may say so — it is
|
|
187
|
+
not a record of anyone having been told. Nothing here notifies (#3129).
|
|
188
|
+
|
|
189
|
+
``.active()`` (#3572): by its own reasoning above this is a capacity read —
|
|
190
|
+
it counts who is carrying load in the plan — so a deactivated person, whose
|
|
191
|
+
assignment rows are retained only for audit, is not in it. That is the line
|
|
192
|
+
against the notification audiences in ``config_notice`` and ``amend``, which
|
|
193
|
+
stay unfiltered: those decide who *receives* something, which is an access
|
|
194
|
+
question and belongs to #3584.
|
|
195
|
+
"""
|
|
196
|
+
from trueppm_api.apps.resources.models import TaskResource
|
|
197
|
+
|
|
198
|
+
return [
|
|
199
|
+
str(rid)
|
|
200
|
+
for rid in TaskResource.objects.active()
|
|
201
|
+
.filter(task__project_id=project.pk, task__is_deleted=False)
|
|
202
|
+
.values_list("resource_id", flat=True)
|
|
203
|
+
.distinct()
|
|
204
|
+
]
|