trueppm-api 0.4.0__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 +27 -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 +346 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +184 -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_squashed_0019_release_0_4.py +302 -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 +143 -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/minter_role.py +38 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +3263 -0
- trueppm_api/apps/access/serializers.py +1007 -0
- trueppm_api/apps/access/services.py +878 -0
- trueppm_api/apps/access/signals.py +171 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +96 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1965 -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/0001_squashed_0003_release_0_4.py +344 -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 +109 -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/0001_squashed_0003_release_0_4.py +90 -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 +459 -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 +178 -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 +863 -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 +597 -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_squashed_0014_release_0_4.py +225 -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/0014_tasklink_created_by.py +27 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +635 -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 +528 -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 +734 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1158 -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 +280 -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 +551 -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 +1276 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +439 -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 +462 -0
- trueppm_api/apps/notifications/email_backend.py +350 -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 +299 -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_squashed_0011_release_0_4.py +204 -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 +1148 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +1094 -0
- trueppm_api/apps/notifications/services.py +1489 -0
- trueppm_api/apps/notifications/tasks.py +762 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +919 -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 +566 -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 +993 -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 +537 -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_squashed_0009_release_0_4.py +128 -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 +541 -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 +458 -0
- trueppm_api/apps/projects/backfill.py +459 -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 +212 -0
- trueppm_api/apps/projects/config_notice.py +1330 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +132 -0
- trueppm_api/apps/projects/decisions_views.py +97 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +423 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -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/key_views.py +468 -0
- trueppm_api/apps/projects/keys.py +205 -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 +514 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -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/migration_helpers.py +75 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -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 +59 -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 +75 -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 +76 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -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/0154_object_keys.py +184 -0
- trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
- trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
- trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +9209 -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 +903 -0
- trueppm_api/apps/projects/program_schedule.py +767 -0
- trueppm_api/apps/projects/program_views.py +3445 -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 +492 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
- trueppm_api/apps/projects/seed/exporter.py +1717 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2764 -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 +128 -0
- trueppm_api/apps/projects/seed/replay.py +1695 -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 +1169 -0
- trueppm_api/apps/projects/serializers.py +12904 -0
- trueppm_api/apps/projects/services.py +7525 -0
- trueppm_api/apps/projects/share_serializers.py +142 -0
- trueppm_api/apps/projects/share_services.py +373 -0
- trueppm_api/apps/projects/share_views.py +395 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +910 -0
- trueppm_api/apps/projects/signal_privacy_views.py +485 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +288 -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 +1244 -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 +1979 -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 +261 -0
- trueppm_api/apps/projects/template_views.py +829 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1016 -0
- trueppm_api/apps/projects/utilization.py +909 -0
- trueppm_api/apps/projects/views.py +21225 -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/0014_squashed_0015_release_0_4.py +47 -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 +1752 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +144 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/finish_reading.py +198 -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/mc_admission.py +181 -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/0010_squashed_0016_release_0_4.py +106 -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/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +542 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +334 -0
- trueppm_api/apps/scheduling/serializers.py +723 -0
- trueppm_api/apps/scheduling/services.py +1251 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +2248 -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 +2908 -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 +446 -0
- trueppm_api/apps/sso/services.py +1473 -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 +369 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +548 -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/0003_squashed_0005_release_0_4.py +77 -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/0006_boardevent_per_project_seq.py +77 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +272 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +488 -0
- trueppm_api/apps/sync/serializers.py +688 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +556 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +957 -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 +159 -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/0004_backfill_default_teams_on_upgrade.py +49 -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 +440 -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/0001_squashed_0004_release_0_4.py +167 -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 +211 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +597 -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 +279 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -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 +100 -0
- trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
- trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +437 -0
- trueppm_api/apps/webhooks/serializers.py +326 -0
- trueppm_api/apps/webhooks/tasks.py +680 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +533 -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 +371 -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 +254 -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_squashed_0027_release_0_4.py +199 -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 +967 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +616 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1581 -0
- trueppm_api/asgi.py +49 -0
- trueppm_api/celery.py +40 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +934 -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/demo_read_only.py +314 -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 +1221 -0
- trueppm_api/core/password_reset.py +513 -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 +1034 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +514 -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/core/worker_selfheal.py +205 -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 +2605 -0
- trueppm_api/settings/dev.py +296 -0
- trueppm_api/settings/prod.py +265 -0
- trueppm_api/urls.py +217 -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.0.dist-info/METADATA +273 -0
- trueppm_api-0.4.0.dist-info/RECORD +799 -0
- trueppm_api-0.4.0.dist-info/WHEEL +4 -0
- trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Typed read/write logic for per-task custom-field values (#2143, ADR-0528).
|
|
2
|
+
|
|
3
|
+
Two responsibilities, both keyed off ``ProjectCustomField.field_type``:
|
|
4
|
+
|
|
5
|
+
* :func:`build_custom_fields_map` — the *read* surface. Turns a task's prefetched
|
|
6
|
+
``TaskCustomFieldValue`` rows into the flat ``{"<field_id>": <json value>}`` map
|
|
7
|
+
the Task payload (and the sync delta) carries. Unset values are omitted entirely
|
|
8
|
+
— no ``null``, no placeholder — so the board card renders nothing for them
|
|
9
|
+
(docs/design/board-card-custom-fields.md §5).
|
|
10
|
+
* :func:`validate_custom_field_write` — the *write* surface. Validates a raw
|
|
11
|
+
``{"value": …}`` payload against the field's type and returns the model-column
|
|
12
|
+
kwargs to persist, raising DRF ``ValidationError`` with the documented 400 shapes
|
|
13
|
+
(enum membership for selects, live-user FK for person, number/date parsing).
|
|
14
|
+
|
|
15
|
+
Kept out of the (very large) ``serializers.py`` so the typed dispatch is unit-testable
|
|
16
|
+
in isolation and shared verbatim by ``TaskSerializer``, ``SyncTaskSerializer``, and
|
|
17
|
+
``TaskCustomFieldValueView`` without a circular import.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
from datetime import date
|
|
24
|
+
from decimal import Decimal, InvalidOperation
|
|
25
|
+
from typing import TYPE_CHECKING, Any
|
|
26
|
+
|
|
27
|
+
from django.contrib.auth import get_user_model
|
|
28
|
+
from django.core.exceptions import ValidationError
|
|
29
|
+
from rest_framework import serializers
|
|
30
|
+
|
|
31
|
+
from trueppm_api.apps.projects.models import (
|
|
32
|
+
CustomFieldType,
|
|
33
|
+
ProjectCustomField,
|
|
34
|
+
TaskCustomFieldValue,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING:
|
|
38
|
+
from trueppm_api.apps.projects.models import Task
|
|
39
|
+
|
|
40
|
+
User = get_user_model()
|
|
41
|
+
|
|
42
|
+
_MULTI_SELECT_MAX = 50 # mirrors the option-list cap on the field definition.
|
|
43
|
+
# NUMBER values persist to a numeric(20,6) column: at most 14 integer digits and 6
|
|
44
|
+
# decimal places. Bound the magnitude and quantize so a crafted value cannot overflow
|
|
45
|
+
# the column (Postgres DataError → HTTP 500) or store excess precision.
|
|
46
|
+
_NUMBER_MAX = Decimal(10) ** 14
|
|
47
|
+
_NUMBER_QUANTUM = Decimal("0.000001")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _person_repr(user: Any) -> dict[str, str]:
|
|
51
|
+
"""Serialize a person value the way the board already renders assignees.
|
|
52
|
+
|
|
53
|
+
``{id, name, initials}`` so the card can draw the avatar + name with no second
|
|
54
|
+
fetch; initials fall back to the username when the name is unset, matching
|
|
55
|
+
``RiskSerializer.get_owner_initials``.
|
|
56
|
+
"""
|
|
57
|
+
name = user.get_full_name() or user.get_username()
|
|
58
|
+
parts: list[str] = []
|
|
59
|
+
if user.first_name:
|
|
60
|
+
parts.append(user.first_name[0].upper())
|
|
61
|
+
if user.last_name:
|
|
62
|
+
parts.append(user.last_name[0].upper())
|
|
63
|
+
initials = "".join(parts[:2]) if parts else user.get_username()[:2].upper()
|
|
64
|
+
return {"id": str(user.pk), "name": name, "initials": initials}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def resolve_custom_field_value(value: TaskCustomFieldValue, field: ProjectCustomField) -> Any:
|
|
68
|
+
"""Resolve one value row to its flat-dict JSON form, or ``None`` when unset.
|
|
69
|
+
|
|
70
|
+
``None`` means "omit from the map" — the caller drops the key so an unset field
|
|
71
|
+
consumes zero card real estate. Note ``BOOLEAN`` returns ``False`` (a real value)
|
|
72
|
+
only when it was explicitly set; a never-set checkbox resolves to ``None``.
|
|
73
|
+
"""
|
|
74
|
+
resolver = _VALUE_RESOLVERS.get(field.field_type)
|
|
75
|
+
return resolver(value) if resolver is not None else None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# Per-type resolvers keyed by ``field_type`` — a flat dispatch table replacing an
|
|
79
|
+
# if-ladder so each type's read shape is one entry and a new type is a one-line add.
|
|
80
|
+
_VALUE_RESOLVERS: dict[str, Callable[[TaskCustomFieldValue], Any]] = {
|
|
81
|
+
CustomFieldType.TEXT: lambda v: v.value_text or None,
|
|
82
|
+
# Decimal → float for clean JSON; the web layer formats (thousands, units).
|
|
83
|
+
CustomFieldType.NUMBER: (
|
|
84
|
+
lambda v: float(v.value_number) if v.value_number is not None else None
|
|
85
|
+
),
|
|
86
|
+
CustomFieldType.DATE: lambda v: v.value_date.isoformat() if v.value_date else None,
|
|
87
|
+
CustomFieldType.SINGLE_SELECT: lambda v: v.value_option or None,
|
|
88
|
+
CustomFieldType.MULTI_SELECT: lambda v: list(v.value_multi) if v.value_multi else None,
|
|
89
|
+
CustomFieldType.BOOLEAN: lambda v: v.value_bool, # True / False / None
|
|
90
|
+
CustomFieldType.USER: lambda v: _person_repr(v.value_user) if v.value_user_id else None,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def build_custom_fields_map(task: Task) -> dict[str, Any]:
|
|
95
|
+
"""Flat ``{"<field_id>": <value>}`` map for a task's populated custom fields.
|
|
96
|
+
|
|
97
|
+
Iterates the ``custom_field_values`` prefetch cache (never a fresh query — the
|
|
98
|
+
read paths prefetch it with ``field`` select-related). Values that resolve to
|
|
99
|
+
``None`` (unset) are omitted, so the map only ever carries set values and an
|
|
100
|
+
unset field consumes zero card real estate.
|
|
101
|
+
"""
|
|
102
|
+
out: dict[str, Any] = {}
|
|
103
|
+
for value in task.custom_field_values.all():
|
|
104
|
+
resolved = resolve_custom_field_value(value, value.field)
|
|
105
|
+
if resolved is not None:
|
|
106
|
+
out[str(value.field_id)] = resolved
|
|
107
|
+
return out
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _clear_kwargs() -> dict[str, Any]:
|
|
111
|
+
"""Column kwargs that null out every typed column (defaults for text/multi)."""
|
|
112
|
+
return {
|
|
113
|
+
"value_text": "",
|
|
114
|
+
"value_number": None,
|
|
115
|
+
"value_date": None,
|
|
116
|
+
"value_option": "",
|
|
117
|
+
"value_multi": [],
|
|
118
|
+
"value_user": None,
|
|
119
|
+
"value_bool": None,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _write_text(field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
124
|
+
if not isinstance(raw, str):
|
|
125
|
+
raise serializers.ValidationError({"value": "value must be a string."})
|
|
126
|
+
if len(raw) > 2000:
|
|
127
|
+
raise serializers.ValidationError({"value": "value must be ≤ 2000 characters."})
|
|
128
|
+
kwargs["value_text"] = raw
|
|
129
|
+
return kwargs
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _write_number(field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
133
|
+
# Reject bools (they are ``int`` subclasses) and un-parseable strings.
|
|
134
|
+
if isinstance(raw, bool) or not isinstance(raw, (int, float, str)):
|
|
135
|
+
raise serializers.ValidationError({"value": "value must be a number."})
|
|
136
|
+
try:
|
|
137
|
+
num = Decimal(str(raw))
|
|
138
|
+
except (InvalidOperation, ValueError):
|
|
139
|
+
raise serializers.ValidationError({"value": "value must be a number."}) from None
|
|
140
|
+
# This is a raw APIView, not a DRF DecimalField — validate the magnitude and
|
|
141
|
+
# finiteness here, or a crafted NaN/Infinity/over-precision value reaches the
|
|
142
|
+
# numeric(20,6) column and raises a Postgres DataError (HTTP 500) or round-trips
|
|
143
|
+
# as spec-invalid JSON ``NaN`` on every subsequent read (the #2123-2127 fuzz-500
|
|
144
|
+
# class; the nightly Schemathesis job is schedule-only and won't catch it).
|
|
145
|
+
if not num.is_finite():
|
|
146
|
+
raise serializers.ValidationError({"value": "value must be a finite number."})
|
|
147
|
+
if abs(num) >= _NUMBER_MAX:
|
|
148
|
+
raise serializers.ValidationError(
|
|
149
|
+
{"value": "value is out of range (at most 14 integer digits)."}
|
|
150
|
+
)
|
|
151
|
+
kwargs["value_number"] = num.quantize(_NUMBER_QUANTUM) # clamp to 6 dp
|
|
152
|
+
return kwargs
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _write_date(field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
156
|
+
if not isinstance(raw, str):
|
|
157
|
+
raise serializers.ValidationError({"value": "value must be an ISO-8601 date string."})
|
|
158
|
+
try:
|
|
159
|
+
kwargs["value_date"] = date.fromisoformat(raw)
|
|
160
|
+
except ValueError:
|
|
161
|
+
raise serializers.ValidationError(
|
|
162
|
+
{"value": "value must be an ISO-8601 date (YYYY-MM-DD)."}
|
|
163
|
+
) from None
|
|
164
|
+
return kwargs
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _write_boolean(field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
168
|
+
if not isinstance(raw, bool):
|
|
169
|
+
raise serializers.ValidationError({"value": "value must be a boolean."})
|
|
170
|
+
kwargs["value_bool"] = raw
|
|
171
|
+
return kwargs
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _write_single_select(
|
|
175
|
+
field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]
|
|
176
|
+
) -> dict[str, Any]:
|
|
177
|
+
if not isinstance(raw, str):
|
|
178
|
+
raise serializers.ValidationError({"value": "value must be an option value string."})
|
|
179
|
+
allowed = {opt.get("value") for opt in field.options}
|
|
180
|
+
if raw not in allowed:
|
|
181
|
+
raise serializers.ValidationError(
|
|
182
|
+
{"value": f"{raw!r} is not a valid option for field {field.name!r}."}
|
|
183
|
+
)
|
|
184
|
+
kwargs["value_option"] = raw
|
|
185
|
+
return kwargs
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _write_multi_select(
|
|
189
|
+
field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]
|
|
190
|
+
) -> dict[str, Any]:
|
|
191
|
+
if not isinstance(raw, list) or not all(isinstance(v, str) for v in raw):
|
|
192
|
+
raise serializers.ValidationError({"value": "value must be a list of option values."})
|
|
193
|
+
if len(raw) > _MULTI_SELECT_MAX:
|
|
194
|
+
raise serializers.ValidationError(
|
|
195
|
+
{"value": f"value must contain at most {_MULTI_SELECT_MAX} options."}
|
|
196
|
+
)
|
|
197
|
+
allowed = {opt.get("value") for opt in field.options}
|
|
198
|
+
for v in raw:
|
|
199
|
+
if v not in allowed:
|
|
200
|
+
raise serializers.ValidationError(
|
|
201
|
+
{"value": f"{v!r} is not a valid option for field {field.name!r}."}
|
|
202
|
+
)
|
|
203
|
+
# De-duplicate while preserving author order — an idempotent, forgiving write.
|
|
204
|
+
kwargs["value_multi"] = list(dict.fromkeys(raw))
|
|
205
|
+
return kwargs
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _write_user(field: ProjectCustomField, raw: Any, kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
209
|
+
if not isinstance(raw, str):
|
|
210
|
+
raise serializers.ValidationError({"value": "value must be a user id string."})
|
|
211
|
+
# The person must be a live member of the field's project — mirrors the
|
|
212
|
+
# assignee membership guard (#684) so a person field cannot smuggle a
|
|
213
|
+
# non-member (or a user from another project) onto the task.
|
|
214
|
+
from trueppm_api.apps.access.models import ProjectMembership
|
|
215
|
+
|
|
216
|
+
# One generic message for both "no such user" and "exists but not a member" so
|
|
217
|
+
# the endpoint is not an existence oracle for arbitrary user ids.
|
|
218
|
+
not_member = serializers.ValidationError(
|
|
219
|
+
{"value": f"user {raw!r} is not a valid member of this project."}
|
|
220
|
+
)
|
|
221
|
+
try:
|
|
222
|
+
user = User.objects.get(pk=raw)
|
|
223
|
+
except (User.DoesNotExist, ValueError, ValidationError):
|
|
224
|
+
raise not_member from None
|
|
225
|
+
# ``live()``, not ``objects`` (#3411): revoking access soft-deletes the membership
|
|
226
|
+
# row against an unconditional (project, user) constraint, so an unfloored ``exists``
|
|
227
|
+
# accepts someone who has already left the project as a valid value for the field.
|
|
228
|
+
is_member = (
|
|
229
|
+
ProjectMembership.live().filter(project_id=field.project_id, user_id=user.pk).exists()
|
|
230
|
+
)
|
|
231
|
+
if not is_member:
|
|
232
|
+
raise not_member
|
|
233
|
+
kwargs["value_user"] = user
|
|
234
|
+
return kwargs
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
# Per-type write validators keyed by ``field_type``. Each takes the field, the raw
|
|
238
|
+
# JSON value and the pre-cleared kwargs, validates and sets exactly one typed column,
|
|
239
|
+
# and returns the kwargs (or raises DRF ``ValidationError`` → HTTP 400). Splitting the
|
|
240
|
+
# old single if-ladder into one function per type keeps each — and the dispatcher —
|
|
241
|
+
# well under the cognitive-complexity budget.
|
|
242
|
+
_VALUE_WRITERS: dict[str, Callable[[ProjectCustomField, Any, dict[str, Any]], dict[str, Any]]] = {
|
|
243
|
+
CustomFieldType.TEXT: _write_text,
|
|
244
|
+
CustomFieldType.NUMBER: _write_number,
|
|
245
|
+
CustomFieldType.DATE: _write_date,
|
|
246
|
+
CustomFieldType.BOOLEAN: _write_boolean,
|
|
247
|
+
CustomFieldType.SINGLE_SELECT: _write_single_select,
|
|
248
|
+
CustomFieldType.MULTI_SELECT: _write_multi_select,
|
|
249
|
+
CustomFieldType.USER: _write_user,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def validate_custom_field_write(field: ProjectCustomField, raw: Any) -> dict[str, Any]:
|
|
254
|
+
"""Validate a raw value against ``field``'s type → model-column kwargs.
|
|
255
|
+
|
|
256
|
+
Raises DRF ``ValidationError`` (HTTP 400) with a ``{"value": …}`` shape on any
|
|
257
|
+
type mismatch. The returned dict always sets exactly one typed column and leaves
|
|
258
|
+
the rest at their empty defaults, so applying it over an existing row cleanly
|
|
259
|
+
overwrites a prior value (the write path is a full upsert, not a partial merge).
|
|
260
|
+
"""
|
|
261
|
+
writer = _VALUE_WRITERS.get(field.field_type)
|
|
262
|
+
if writer is None:
|
|
263
|
+
raise serializers.ValidationError(
|
|
264
|
+
{"value": f"unsupported field type {field.field_type!r}."}
|
|
265
|
+
)
|
|
266
|
+
return writer(field, raw, _clear_kwargs())
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Decisions-view visibility gate + queryset helpers (ADR-0167, #748).
|
|
2
|
+
|
|
3
|
+
The Decisions view surfaces every task note flagged ``decision=True`` for a project,
|
|
4
|
+
optionally scoped to one sprint. Visibility is team-owned: by default the list is
|
|
5
|
+
readable by the team and the PM only; an *oversight reader* (a project member who is
|
|
6
|
+
not on the default team and is below Admin) sees it only once a project Admin opts in
|
|
7
|
+
via :class:`~trueppm_api.apps.projects.models.ProjectDecisionsPolicy`.
|
|
8
|
+
|
|
9
|
+
These helpers are pure (no view imports) so both the ``ProjectViewSet.decisions``
|
|
10
|
+
list action and the consent ``ProjectDecisionsPolicyView`` can share the gate without
|
|
11
|
+
an import cycle.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from django.db import transaction
|
|
19
|
+
from django.db.models import F, QuerySet
|
|
20
|
+
|
|
21
|
+
from trueppm_api.apps.access.models import Role
|
|
22
|
+
from trueppm_api.apps.access.permissions import _membership_role
|
|
23
|
+
from trueppm_api.apps.projects.models import Project, ProjectDecisionsPolicy, TaskNote
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_or_create_decisions_policy(project: Project) -> ProjectDecisionsPolicy:
|
|
27
|
+
"""Lazily materialize the project's decisions-visibility policy.
|
|
28
|
+
|
|
29
|
+
Mirrors ``signal_privacy_services.get_or_create_policy`` — the row is created on
|
|
30
|
+
first access so existing projects need no data migration. A fresh row carries the
|
|
31
|
+
default-closed posture (``oversight_visible=False``).
|
|
32
|
+
"""
|
|
33
|
+
policy, _ = ProjectDecisionsPolicy.objects.get_or_create(project=project)
|
|
34
|
+
return policy
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def set_oversight_visible(
|
|
38
|
+
policy: ProjectDecisionsPolicy, oversight_visible: bool
|
|
39
|
+
) -> ProjectDecisionsPolicy:
|
|
40
|
+
"""Flip the team's oversight-visibility switch and notify live clients (#4283).
|
|
41
|
+
|
|
42
|
+
Mirrors ``signal_privacy_services.set_signal_audience``'s layering: the write and
|
|
43
|
+
its live-client broadcast live in the one chokepoint every writer of this field
|
|
44
|
+
passes through, so a future caller can never persist the switch without
|
|
45
|
+
broadcasting it — the defect this closes (``ProjectDecisionsPolicyView.patch``
|
|
46
|
+
saved the policy with no broadcast anywhere in the write path). Plain ``save()``
|
|
47
|
+
(not ``update_fields``) so ``VersionedModel`` bumps ``server_version`` and
|
|
48
|
+
``HistoricalRecords`` captures who flipped the switch.
|
|
49
|
+
"""
|
|
50
|
+
policy.oversight_visible = oversight_visible
|
|
51
|
+
policy.save()
|
|
52
|
+
_emit_oversight_visible_changed(policy.project_id, oversight_visible)
|
|
53
|
+
return policy
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _emit_oversight_visible_changed(project_id: Any, oversight_visible: bool) -> None:
|
|
57
|
+
"""Broadcast the oversight-visibility change on commit (#4283).
|
|
58
|
+
|
|
59
|
+
Mirrors ``signal_privacy_services._emit_consent_changed``'s deferral: the write
|
|
60
|
+
must commit before a peer's refetch could observe the new value, so the
|
|
61
|
+
broadcast is deferred with ``transaction.on_commit()`` per project convention.
|
|
62
|
+
"""
|
|
63
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
64
|
+
|
|
65
|
+
project_id_str = str(project_id)
|
|
66
|
+
transaction.on_commit(
|
|
67
|
+
lambda: broadcast_board_event(
|
|
68
|
+
project_id_str,
|
|
69
|
+
"decisions_policy_changed",
|
|
70
|
+
{"id": project_id_str, "oversight_visible": oversight_visible},
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def can_read_decisions(
|
|
76
|
+
request: Any,
|
|
77
|
+
project_id: Any,
|
|
78
|
+
*,
|
|
79
|
+
policy: ProjectDecisionsPolicy | None = None,
|
|
80
|
+
) -> bool:
|
|
81
|
+
"""Whether the requester may read the project's Decisions view (ADR-0167 §3).
|
|
82
|
+
|
|
83
|
+
"Team + PM" = ``Role.MEMBER`` (Team Member) and above — the project's contributors and
|
|
84
|
+
managers, who see the log by default. A requester below the Member band — a Viewer, or
|
|
85
|
+
an Enterprise read-augmented/auditor role in the reserved 1–99 ordinal band (ADR-0072) —
|
|
86
|
+
is the single-project stand-in for a PMO/oversight reader and is suppressed until the
|
|
87
|
+
team opts in (``oversight_visible``).
|
|
88
|
+
|
|
89
|
+
Role, not team membership, is the discriminator: every ``ProjectMembership`` mirrors
|
|
90
|
+
onto the default team (teams/signals.py), so ``is_team_member`` cannot tell a
|
|
91
|
+
read-only observer apart from a contributor — the Role ladder can. ``policy`` may be
|
|
92
|
+
passed to avoid a second lookup; it is fetched lazily otherwise (read path, no row
|
|
93
|
+
creation).
|
|
94
|
+
"""
|
|
95
|
+
role = _membership_role(request, project_id)
|
|
96
|
+
if role is None:
|
|
97
|
+
return False
|
|
98
|
+
if role >= Role.MEMBER:
|
|
99
|
+
return True
|
|
100
|
+
if policy is None:
|
|
101
|
+
# Read path: do not create the row on a denied/allowed check. Absent row ⇒
|
|
102
|
+
# default-closed, so a missing policy resolves to "not oversight-visible".
|
|
103
|
+
oversight_visible = (
|
|
104
|
+
ProjectDecisionsPolicy.objects.filter(project_id=project_id)
|
|
105
|
+
.values_list("oversight_visible", flat=True)
|
|
106
|
+
.first()
|
|
107
|
+
or False
|
|
108
|
+
)
|
|
109
|
+
else:
|
|
110
|
+
oversight_visible = policy.oversight_visible
|
|
111
|
+
return bool(oversight_visible)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def decision_notes_queryset(project_id: Any, sprint_id: str | None = None) -> QuerySet[TaskNote]:
|
|
115
|
+
"""Decision-flagged, non-deleted notes for a project, optionally one sprint.
|
|
116
|
+
|
|
117
|
+
Ordered newest-sprint-first then newest-decision-first so the web client can group
|
|
118
|
+
by sprint with the most recent sprint (and the backlog group, sprint null) leading.
|
|
119
|
+
``select_related`` pulls the task + sprint + author so the serializer renders the
|
|
120
|
+
task/sprint context and the author mini without an N+1.
|
|
121
|
+
"""
|
|
122
|
+
qs = TaskNote.objects.filter(
|
|
123
|
+
task__project_id=project_id,
|
|
124
|
+
is_deleted=False,
|
|
125
|
+
decision=True,
|
|
126
|
+
).select_related("author", "task", "task__sprint")
|
|
127
|
+
if sprint_id is not None:
|
|
128
|
+
qs = qs.filter(task__sprint_id=sprint_id)
|
|
129
|
+
# NULLS LAST on sprint start so the backlog (sprint-less) group trails the dated
|
|
130
|
+
# sprints — Postgres sorts NULLs first under a bare DESC, so make it explicit;
|
|
131
|
+
# -created_at orders decisions newest-first within each sprint.
|
|
132
|
+
return qs.order_by(F("task__sprint__start_date").desc(nulls_last=True), "-created_at")
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Decisions-view visibility consent endpoint (ADR-0167 §3, #748).
|
|
2
|
+
|
|
3
|
+
Surface:
|
|
4
|
+
GET /api/v1/projects/<project_pk>/decisions-policy/ — read posture (+ can_edit)
|
|
5
|
+
PATCH /api/v1/projects/<project_pk>/decisions-policy/ — set oversight_visible (Admin+)
|
|
6
|
+
|
|
7
|
+
The single ``oversight_visible`` switch is the team's upward-exposure control for the
|
|
8
|
+
project Decisions view. Default-closed; widening to oversight readers is a project-Admin
|
|
9
|
+
act (the team-admin authority in the OSS single-project model). The matching read gate is
|
|
10
|
+
``decisions_services.can_read_decisions``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from drf_spectacular.utils import extend_schema
|
|
18
|
+
from rest_framework import serializers
|
|
19
|
+
from rest_framework.exceptions import PermissionDenied
|
|
20
|
+
from rest_framework.generics import get_object_or_404
|
|
21
|
+
from rest_framework.permissions import IsAuthenticated
|
|
22
|
+
from rest_framework.request import Request
|
|
23
|
+
from rest_framework.response import Response
|
|
24
|
+
from rest_framework.views import APIView
|
|
25
|
+
|
|
26
|
+
from trueppm_api.apps.access.models import Role
|
|
27
|
+
from trueppm_api.apps.access.permissions import (
|
|
28
|
+
IsProjectMember,
|
|
29
|
+
IsProjectNotArchived,
|
|
30
|
+
_membership_role,
|
|
31
|
+
)
|
|
32
|
+
from trueppm_api.apps.projects.decisions_services import (
|
|
33
|
+
get_or_create_decisions_policy,
|
|
34
|
+
set_oversight_visible,
|
|
35
|
+
)
|
|
36
|
+
from trueppm_api.apps.projects.models import Project
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class DecisionsPolicyReadSerializer(serializers.Serializer[Any]):
|
|
40
|
+
"""Read shape: the current posture plus whether the requester may change it."""
|
|
41
|
+
|
|
42
|
+
oversight_visible = serializers.BooleanField()
|
|
43
|
+
can_edit = serializers.BooleanField()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DecisionsPolicyWriteSerializer(serializers.Serializer[Any]):
|
|
47
|
+
"""PATCH body — flip the team's single oversight-visibility switch."""
|
|
48
|
+
|
|
49
|
+
oversight_visible = serializers.BooleanField()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ProjectDecisionsPolicyView(APIView):
|
|
53
|
+
"""GET the decisions-visibility posture; PATCH ``oversight_visible`` (Admin+)."""
|
|
54
|
+
|
|
55
|
+
permission_classes = [IsAuthenticated, IsProjectMember, IsProjectNotArchived] # noqa: RUF012
|
|
56
|
+
|
|
57
|
+
def _project(self, project_pk: str) -> Project:
|
|
58
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
59
|
+
self.check_object_permissions(self.request, project)
|
|
60
|
+
return project
|
|
61
|
+
|
|
62
|
+
def _payload(
|
|
63
|
+
self, request: Request, project_id: Any, oversight_visible: bool
|
|
64
|
+
) -> dict[str, Any]:
|
|
65
|
+
role = _membership_role(request, project_id)
|
|
66
|
+
can_edit = role is not None and role >= Role.ADMIN
|
|
67
|
+
return {"oversight_visible": oversight_visible, "can_edit": can_edit}
|
|
68
|
+
|
|
69
|
+
@extend_schema(
|
|
70
|
+
summary="Read the Decisions-view visibility posture",
|
|
71
|
+
responses=DecisionsPolicyReadSerializer,
|
|
72
|
+
)
|
|
73
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
74
|
+
project = self._project(project_pk)
|
|
75
|
+
policy = get_or_create_decisions_policy(project)
|
|
76
|
+
return Response(self._payload(request, project.pk, policy.oversight_visible))
|
|
77
|
+
|
|
78
|
+
@extend_schema(
|
|
79
|
+
summary="Set whether oversight readers may see the Decisions view (Admin+)",
|
|
80
|
+
request=DecisionsPolicyWriteSerializer,
|
|
81
|
+
responses=DecisionsPolicyReadSerializer,
|
|
82
|
+
)
|
|
83
|
+
def patch(self, request: Request, project_pk: str) -> Response:
|
|
84
|
+
project = self._project(project_pk)
|
|
85
|
+
role = _membership_role(request, project.pk)
|
|
86
|
+
# Team-admin consent (ADR-0167 §3): only a project Admin+ may widen or narrow the
|
|
87
|
+
# team's upward exposure. Read stays any-member; the write is the consent act.
|
|
88
|
+
if role is None or role < Role.ADMIN:
|
|
89
|
+
raise PermissionDenied("Only a project admin can change who sees the Decisions view.")
|
|
90
|
+
body = DecisionsPolicyWriteSerializer(data=request.data)
|
|
91
|
+
body.is_valid(raise_exception=True)
|
|
92
|
+
policy = get_or_create_decisions_policy(project)
|
|
93
|
+
# set_oversight_visible is the one chokepoint that both saves (bumping
|
|
94
|
+
# server_version / HistoricalRecords) and broadcasts decisions_policy_changed
|
|
95
|
+
# on commit (#4283) — mirrors signal_privacy_services' write+broadcast layering.
|
|
96
|
+
policy = set_oversight_visible(policy, body.validated_data["oversight_visible"])
|
|
97
|
+
return Response(self._payload(request, project.pk, policy.oversight_visible))
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Workspace → Program → Project resolution for the estimation scale (ADR-0510, #2027).
|
|
2
|
+
|
|
3
|
+
Story-point estimates are stored as a single integer (``Task.story_points`` /
|
|
4
|
+
``BacklogItem.story_points``). The ``estimation_scale`` enum decides only which
|
|
5
|
+
input widget and display label the client renders for that integer — Fibonacci,
|
|
6
|
+
Linear, or T-shirt — never the stored value or any velocity/rollup math. The scale
|
|
7
|
+
is set at the workspace by default and may be overridden per program or project.
|
|
8
|
+
The *effective* scale is resolved here, computed-on-read (ADR-0108) — there is no
|
|
9
|
+
stored/denormalized effective column to keep in sync. Clients (web, mobile, MCP)
|
|
10
|
+
read the serializer's ``effective_estimation_scale``; they never re-implement this
|
|
11
|
+
precedence.
|
|
12
|
+
|
|
13
|
+
Precedence: project override → program override → workspace value.
|
|
14
|
+
|
|
15
|
+
Unlike the calendar (ADR-0441) and duration policy (ADR-0151), the estimation scale
|
|
16
|
+
has **no enforcement seam** — it is a plain PO/team preference, freely overridable
|
|
17
|
+
at every scope, and OSS by construction (ADR-0510, enterprise-check). Adding a lock
|
|
18
|
+
flag, override-suppression, or an inheritance-change audit would import the
|
|
19
|
+
Enterprise "policy-enforced inheritance" surface and belongs in trueppm-enterprise.
|
|
20
|
+
The Workspace root is non-null (default Fibonacci), so the chain always resolves to
|
|
21
|
+
a real value; there is no ``system_default`` terminal (contrast the calendar's
|
|
22
|
+
Mon-Fri/8h/UTC backstop).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import TYPE_CHECKING, Literal
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
31
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
32
|
+
|
|
33
|
+
#: The inheritable field, identically named on Workspace (non-null root), Program,
|
|
34
|
+
#: and Project (nullable override).
|
|
35
|
+
ESTIMATION_SCALE_FIELD = "estimation_scale"
|
|
36
|
+
|
|
37
|
+
#: Which scope supplied the effective scale. No ``system_default`` — the Workspace
|
|
38
|
+
#: root is non-null, so resolution always terminates in a real scope.
|
|
39
|
+
EstimationScaleSource = Literal["project", "program", "workspace"]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _program_of(obj: Program | Project) -> Program | None:
|
|
43
|
+
"""The owning program of a project, or None for a program/standalone project."""
|
|
44
|
+
from trueppm_api.apps.projects.models import Program
|
|
45
|
+
|
|
46
|
+
if isinstance(obj, Program):
|
|
47
|
+
return None
|
|
48
|
+
return obj.program if obj.program_id else None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _parent_value(obj: Program | Project, *, workspace: Workspace) -> str:
|
|
52
|
+
"""Resolve the scale from ``obj``'s parent scope up to the workspace.
|
|
53
|
+
|
|
54
|
+
A Program's parent is the workspace. A Project's parent is its program (whose
|
|
55
|
+
own override resolves up to the workspace) or, if standalone, the workspace.
|
|
56
|
+
"""
|
|
57
|
+
program = _program_of(obj)
|
|
58
|
+
if program is not None:
|
|
59
|
+
program_override = getattr(program, ESTIMATION_SCALE_FIELD)
|
|
60
|
+
if program_override:
|
|
61
|
+
return str(program_override)
|
|
62
|
+
return str(getattr(workspace, ESTIMATION_SCALE_FIELD))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resolve_effective_estimation_scale(
|
|
66
|
+
obj: Program | Project,
|
|
67
|
+
*,
|
|
68
|
+
workspace: Workspace | None = None,
|
|
69
|
+
) -> str:
|
|
70
|
+
"""Resolve the effective estimation scale for a program or project.
|
|
71
|
+
|
|
72
|
+
``workspace`` may be passed to avoid re-loading the singleton per object when
|
|
73
|
+
resolving a list (the serializer caches it once); otherwise it is loaded here.
|
|
74
|
+
Returns an :class:`~trueppm_api.apps.projects.models.EstimationScale` value
|
|
75
|
+
(``fibonacci`` | ``linear`` | ``tshirt``).
|
|
76
|
+
"""
|
|
77
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
78
|
+
|
|
79
|
+
if workspace is None:
|
|
80
|
+
workspace = Workspace.load()
|
|
81
|
+
|
|
82
|
+
own = getattr(obj, ESTIMATION_SCALE_FIELD) # nullable override on Program/Project
|
|
83
|
+
if own:
|
|
84
|
+
return str(own)
|
|
85
|
+
return _parent_value(obj, workspace=workspace)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def resolve_inherited_estimation_scale(
|
|
89
|
+
obj: Program | Project,
|
|
90
|
+
*,
|
|
91
|
+
workspace: Workspace | None = None,
|
|
92
|
+
) -> str:
|
|
93
|
+
"""Scale ``obj`` would inherit if its own override were cleared.
|
|
94
|
+
|
|
95
|
+
Drives a settings "Inherit (<value>)" affordance — it deliberately skips
|
|
96
|
+
``obj``'s own override and resolves from the parent scope up.
|
|
97
|
+
"""
|
|
98
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
99
|
+
|
|
100
|
+
if workspace is None:
|
|
101
|
+
workspace = Workspace.load()
|
|
102
|
+
return _parent_value(obj, workspace=workspace)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def resolve_estimation_scale_source(
|
|
106
|
+
obj: Program | Project,
|
|
107
|
+
*,
|
|
108
|
+
workspace: Workspace | None = None,
|
|
109
|
+
) -> EstimationScaleSource:
|
|
110
|
+
"""Which scope supplied :func:`resolve_effective_estimation_scale` for ``obj``.
|
|
111
|
+
|
|
112
|
+
The breadcrumb the settings UI reads to explain which scope set the scale,
|
|
113
|
+
without re-deriving the precedence. ``workspace`` is accepted for signature
|
|
114
|
+
symmetry with the other resolvers (it is never needed — the terminal scope is
|
|
115
|
+
"workspace" by elimination).
|
|
116
|
+
"""
|
|
117
|
+
from trueppm_api.apps.projects.models import Program
|
|
118
|
+
|
|
119
|
+
if not isinstance(obj, Program) and getattr(obj, ESTIMATION_SCALE_FIELD):
|
|
120
|
+
return "project"
|
|
121
|
+
if isinstance(obj, Program) and getattr(obj, ESTIMATION_SCALE_FIELD):
|
|
122
|
+
return "program"
|
|
123
|
+
program = _program_of(obj)
|
|
124
|
+
if program is not None and getattr(program, ESTIMATION_SCALE_FIELD):
|
|
125
|
+
return "program"
|
|
126
|
+
return "workspace"
|