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,395 @@
|
|
|
1
|
+
"""API views for public read-only board share links (#283, ADR-0245).
|
|
2
|
+
|
|
3
|
+
Three authenticated management routes (Admin+, project-scoped) plus one public,
|
|
4
|
+
unauthenticated, read-only board endpoint. The public view mirrors
|
|
5
|
+
``InviteAcceptView``: ``AllowAny`` + no authentication + a dedicated anon throttle
|
|
6
|
+
+ enumeration-safe generic errors.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from django.shortcuts import get_object_or_404
|
|
17
|
+
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
|
18
|
+
from rest_framework import status
|
|
19
|
+
from rest_framework.permissions import AllowAny, BasePermission, IsAuthenticated
|
|
20
|
+
from rest_framework.request import Request
|
|
21
|
+
from rest_framework.response import Response
|
|
22
|
+
from rest_framework.throttling import AnonRateThrottle, BaseThrottle, UserRateThrottle
|
|
23
|
+
from rest_framework.views import APIView
|
|
24
|
+
|
|
25
|
+
from trueppm_api.apps.access.permissions import (
|
|
26
|
+
IsNotTokenAuthenticated,
|
|
27
|
+
IsProjectAdmin,
|
|
28
|
+
IsProjectNotArchived,
|
|
29
|
+
)
|
|
30
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
31
|
+
from trueppm_api.apps.projects import share_services
|
|
32
|
+
from trueppm_api.apps.projects.models import Project, ShareContentKind, ShareLink
|
|
33
|
+
from trueppm_api.apps.projects.share_serializers import (
|
|
34
|
+
ShareLinkCreateResponseSerializer,
|
|
35
|
+
ShareLinkCreateSerializer,
|
|
36
|
+
ShareLinkSerializer,
|
|
37
|
+
)
|
|
38
|
+
from trueppm_api.apps.projects.sharing_settings import (
|
|
39
|
+
public_sharing_instance_enabled,
|
|
40
|
+
resolve_effective_sharing,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Uniform 404 detail — the kill switch, an unknown token, and a policy-disabled
|
|
44
|
+
# link must all be indistinguishable to the recipient.
|
|
45
|
+
_LINK_UNAVAILABLE_DETAIL = "This share link isn't available."
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ShareLinkMintThrottle(UserRateThrottle):
|
|
49
|
+
"""Caps how fast one account can mint share links (bounds link-spraying)."""
|
|
50
|
+
|
|
51
|
+
scope = "share_mint"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ShareLinkAccessThrottle(AnonRateThrottle):
|
|
55
|
+
"""Defense-in-depth rate limit on the unauthenticated public board endpoint.
|
|
56
|
+
|
|
57
|
+
The 256-bit token is already non-enumerable; this bounds scraping/abuse of a
|
|
58
|
+
legitimately-shared (or leaked) link so it can't become an unauthenticated,
|
|
59
|
+
unthrottled load source on a self-hosted box (Omar's VoC concern).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
scope = "share_access"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _sharing_enabled() -> bool:
|
|
66
|
+
"""Instance kill switch (ADR-0245). Operators disable public sharing org-wide."""
|
|
67
|
+
return public_sharing_instance_enabled()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _public_sharing_allowed(project: Project) -> bool:
|
|
71
|
+
"""Effective public-sharing policy (ADR-0135) for ``project`` — resolved through
|
|
72
|
+
workspace → program → project inheritance, honoring any Enterprise org-wide
|
|
73
|
+
enforcement lock. This is the admin-level "Public sharing" toggle on the General
|
|
74
|
+
settings page; both minting and serving a public board link require it to be on,
|
|
75
|
+
layered on top of the operator-level instance kill switch above. Turning it off
|
|
76
|
+
therefore blocks new links AND immediately stops existing ones from resolving.
|
|
77
|
+
"""
|
|
78
|
+
return resolve_effective_sharing(project, "public_sharing")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ProjectShareLinkListCreateView(IdempotencyMixin, APIView):
|
|
82
|
+
"""GET/POST ``/api/v1/projects/{project_pk}/share-links/`` — Admin+ only.
|
|
83
|
+
|
|
84
|
+
GET lists the project's *active* links (revoked links drop out). POST mints a
|
|
85
|
+
new link and returns the raw token exactly once. Idempotency-Key support (ADR-0170)
|
|
86
|
+
protects the mint from a duplicate link on a client retry.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
permission_classes = [IsAuthenticated, IsProjectAdmin] # noqa: RUF012
|
|
90
|
+
|
|
91
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
92
|
+
# Minting a new public link is a write, so block it on an archived
|
|
93
|
+
# (hard read-only) project — matching the ProjectApiToken convention.
|
|
94
|
+
# Listing stays allowed on an archived project (it is a read).
|
|
95
|
+
perms: list[BasePermission] = [IsAuthenticated(), IsProjectAdmin()]
|
|
96
|
+
if self.request.method == "POST":
|
|
97
|
+
perms.append(IsProjectNotArchived())
|
|
98
|
+
# IsNotTokenAuthenticated (#2939): minting a share link is minting a
|
|
99
|
+
# durable public-access grant, same shape as #2878's PAT-manages-PAT
|
|
100
|
+
# rule. Scoped to POST — GET only returns the ShareLinkSerializer's
|
|
101
|
+
# metadata (never the raw token), so listing isn't a reconnaissance
|
|
102
|
+
# step the way /me/api-tokens/'s GET is.
|
|
103
|
+
perms.append(IsNotTokenAuthenticated())
|
|
104
|
+
return perms
|
|
105
|
+
|
|
106
|
+
def get_throttles(self) -> list[BaseThrottle]:
|
|
107
|
+
# Only the mint (POST) is rate-limited; listing rides the default user rate.
|
|
108
|
+
if self.request.method == "POST":
|
|
109
|
+
return [ShareLinkMintThrottle()]
|
|
110
|
+
return super().get_throttles()
|
|
111
|
+
|
|
112
|
+
@extend_schema(
|
|
113
|
+
summary="List active board share links for a project",
|
|
114
|
+
responses={200: ShareLinkSerializer(many=True)},
|
|
115
|
+
)
|
|
116
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
117
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
118
|
+
self.check_object_permissions(request, project)
|
|
119
|
+
# select_related: ``created_by_name`` reads the creator's name per row.
|
|
120
|
+
links = project.share_links.filter(revoked_at__isnull=True).select_related("created_by")
|
|
121
|
+
return Response(ShareLinkSerializer(links, many=True).data)
|
|
122
|
+
|
|
123
|
+
@extend_schema(
|
|
124
|
+
summary="Create a board share link (returns the raw token once)",
|
|
125
|
+
request=ShareLinkCreateSerializer,
|
|
126
|
+
responses={
|
|
127
|
+
201: ShareLinkCreateResponseSerializer,
|
|
128
|
+
403: OpenApiResponse(description="Public board sharing is disabled on this instance."),
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
def post(self, request: Request, project_pk: str) -> Response:
|
|
132
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
133
|
+
self.check_object_permissions(request, project)
|
|
134
|
+
if not _sharing_enabled():
|
|
135
|
+
return Response(
|
|
136
|
+
{"detail": "Public board sharing is disabled on this instance."},
|
|
137
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
138
|
+
)
|
|
139
|
+
if not _public_sharing_allowed(project):
|
|
140
|
+
# The admin-level "Public sharing" policy (ADR-0135) is off for this
|
|
141
|
+
# project (or an ancestor / an Enterprise org-wide lock) — respect it.
|
|
142
|
+
return Response(
|
|
143
|
+
{"detail": "Public sharing is turned off for this project."},
|
|
144
|
+
status=status.HTTP_403_FORBIDDEN,
|
|
145
|
+
)
|
|
146
|
+
write = ShareLinkCreateSerializer(data=request.data)
|
|
147
|
+
write.is_valid(raise_exception=True)
|
|
148
|
+
link, raw = share_services.mint_share_link(
|
|
149
|
+
project,
|
|
150
|
+
request.user,
|
|
151
|
+
label=write.validated_data["label"],
|
|
152
|
+
show_assignees=write.validated_data["show_assignees"],
|
|
153
|
+
show_milestone_dates=write.validated_data["show_milestone_dates"],
|
|
154
|
+
content_kind=write.validated_data["content_kind"],
|
|
155
|
+
expires_at=write.validated_data["expires_at"],
|
|
156
|
+
)
|
|
157
|
+
data: dict[str, Any] = dict(ShareLinkSerializer(link).data)
|
|
158
|
+
# One-time reveal: the raw token and its relative path exist only here. The
|
|
159
|
+
# web client composes the absolute URL from its own origin. The path segment
|
|
160
|
+
# mirrors content_kind (/share/board/… or /share/schedule/…), but the token's
|
|
161
|
+
# authority is bound to the (hash, content_kind) tuple, not the route.
|
|
162
|
+
data["token"] = raw
|
|
163
|
+
data["share_path"] = f"/share/{link.content_kind}/{raw}"
|
|
164
|
+
return Response(data, status=status.HTTP_201_CREATED)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ProjectShareLinkRevokeView(IdempotencyMixin, APIView):
|
|
168
|
+
"""POST ``/api/v1/projects/{project_pk}/share-links/{link_id}/revoke/`` — Admin+.
|
|
169
|
+
|
|
170
|
+
Idempotent soft-revoke. Works regardless of the instance kill switch (an
|
|
171
|
+
operator disabling sharing should not strand an Admin from revoking an old link).
|
|
172
|
+
Also carries the Idempotency-Key mixin (ADR-0170) like every other unsafe-method
|
|
173
|
+
TruePPM view — mirrors ``ProjectApiTokenViewSet.destroy``, which is naturally
|
|
174
|
+
idempotent too but still opts in rather than claiming ``idempotency_exempt``.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
permission_classes = [IsAuthenticated, IsProjectAdmin] # noqa: RUF012
|
|
178
|
+
# Exempt from the archived-write invariant (#3414), for the same reason the kill
|
|
179
|
+
# switch does not block it: revocation REMOVES access, and an archived project's
|
|
180
|
+
# share links keep serving. `_serve_public_share` 410s a link whose project is in
|
|
181
|
+
# Trash, not one whose project is archived — so gating revoke would leave an Admin
|
|
182
|
+
# watching a live public link on a frozen plan with no way to kill it short of
|
|
183
|
+
# unarchiving. Minting a link IS gated: `ProjectShareLinkListCreateView` carries
|
|
184
|
+
# `IsProjectNotArchived`.
|
|
185
|
+
archived_write_exempt = (
|
|
186
|
+
"revocation only removes access; an archived project's links keep serving, so "
|
|
187
|
+
"blocking revoke would strand an Admin with a live public link"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
@extend_schema(
|
|
191
|
+
summary="Revoke a board share link",
|
|
192
|
+
request=None,
|
|
193
|
+
responses={
|
|
194
|
+
200: ShareLinkSerializer,
|
|
195
|
+
404: OpenApiResponse(description="No such share link on this project."),
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
def post(self, request: Request, project_pk: str, link_id: str) -> Response:
|
|
199
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
200
|
+
self.check_object_permissions(request, project)
|
|
201
|
+
link = get_object_or_404(ShareLink, pk=link_id, project=project)
|
|
202
|
+
share_services.revoke_share_link(link, request.user)
|
|
203
|
+
link.refresh_from_db()
|
|
204
|
+
return Response(ShareLinkSerializer(link).data)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _serve_public_share(
|
|
208
|
+
request: Request,
|
|
209
|
+
token: str,
|
|
210
|
+
content_kind: str,
|
|
211
|
+
serialize: Callable[[ShareLink], dict[str, Any]],
|
|
212
|
+
) -> Response:
|
|
213
|
+
"""Shared serve path for a public read-only share token (#283, #1486).
|
|
214
|
+
|
|
215
|
+
Board and schedule expose different projections but share an identical security
|
|
216
|
+
envelope: instance kill switch → uniform ``404``; unknown/wrong-kind token →
|
|
217
|
+
``404``; revoked / expired / project in Trash → ``410``; ADR-0135 policy off
|
|
218
|
+
→ ``404``; otherwise a weak
|
|
219
|
+
``ETag`` + ``private, max-age=30`` so embeds re-poll cheaply. The wrong-kind
|
|
220
|
+
firewall is the ``content_kind`` filter inside ``resolve_share_link``: a board
|
|
221
|
+
token resolved here as ``SCHEDULE`` (or vice-versa) returns ``None`` → ``404``.
|
|
222
|
+
"""
|
|
223
|
+
# Kill switch → uniform 404 (do not reveal the feature exists, and
|
|
224
|
+
# retroactively disable every existing link while off).
|
|
225
|
+
if not _sharing_enabled():
|
|
226
|
+
return Response(
|
|
227
|
+
{"detail": _LINK_UNAVAILABLE_DETAIL},
|
|
228
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
link = share_services.resolve_share_link(token, content_kind)
|
|
232
|
+
if link is None:
|
|
233
|
+
return Response(
|
|
234
|
+
{"detail": _LINK_UNAVAILABLE_DETAIL},
|
|
235
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
236
|
+
)
|
|
237
|
+
if link.revoked_at is not None:
|
|
238
|
+
return Response(
|
|
239
|
+
{"detail": "This share link has been revoked."},
|
|
240
|
+
status=status.HTTP_410_GONE,
|
|
241
|
+
)
|
|
242
|
+
if link.created_by is not None and not link.created_by.is_active:
|
|
243
|
+
# Deactivation is a termination signal exactly like revocation (#4006):
|
|
244
|
+
# `_revoke_offboarded_credentials` already revokes a link *row* when a
|
|
245
|
+
# member is off-boarded through the app, but a deactivation that happens
|
|
246
|
+
# any other way — Django admin, a shell, a future SCIM deprovision — never
|
|
247
|
+
# calls it, and this view never re-checked the creator afterward. A link
|
|
248
|
+
# minted by an account that is not active keeps serving forever unless
|
|
249
|
+
# something remembers to revoke the row too. Checked at serve time (like
|
|
250
|
+
# `link.project.is_deleted` below) rather than by revoking on deactivation,
|
|
251
|
+
# so no code path has to remember to do it. `created_by` is nullable
|
|
252
|
+
# (`on_delete=SET_NULL`) — a link whose creator was hard-deleted has no
|
|
253
|
+
# creator left to have been deactivated, so it is not covered here.
|
|
254
|
+
#
|
|
255
|
+
# 410, not 404: same "intentionally gone" family as revocation — the
|
|
256
|
+
# recipient already held a valid link and gets the same signal a revoke
|
|
257
|
+
# would have produced.
|
|
258
|
+
return Response(
|
|
259
|
+
{"detail": "This share link has been revoked."},
|
|
260
|
+
status=status.HTTP_410_GONE,
|
|
261
|
+
)
|
|
262
|
+
if link.is_expired:
|
|
263
|
+
# Expiry and revocation are both "intentionally gone" → 410 (not 404), so
|
|
264
|
+
# the recipient knows the link was real and asks the owner for a new one.
|
|
265
|
+
return Response(
|
|
266
|
+
{"detail": "This share link has expired."},
|
|
267
|
+
status=status.HTTP_410_GONE,
|
|
268
|
+
)
|
|
269
|
+
if link.project.is_deleted:
|
|
270
|
+
# Trash means gone, publicly too (#2607). Moving a project to Trash used
|
|
271
|
+
# to leave its public links serving, so an owner who "removed" a project
|
|
272
|
+
# was still publishing it to anyone holding a link.
|
|
273
|
+
#
|
|
274
|
+
# 410 rather than 404: this is the same "intentionally gone" family as
|
|
275
|
+
# revocation and expiry, and it reveals nothing — the recipient already
|
|
276
|
+
# held a valid link to this project.
|
|
277
|
+
#
|
|
278
|
+
# Checked at read time rather than by revoking the links on delete, so
|
|
279
|
+
# restoring from Trash restores exactly the link state the project had
|
|
280
|
+
# (a revoked link stays revoked, an active one resumes). A revoke-on-
|
|
281
|
+
# delete would have to remember which links it revoked in order to undo
|
|
282
|
+
# itself, and would get that wrong the first time someone revoked a link
|
|
283
|
+
# by hand while the project sat in Trash.
|
|
284
|
+
return Response(
|
|
285
|
+
{"detail": "This project has been moved to Trash."},
|
|
286
|
+
status=status.HTTP_410_GONE,
|
|
287
|
+
)
|
|
288
|
+
if not _public_sharing_allowed(link.project):
|
|
289
|
+
# The "Public sharing" policy (ADR-0135) was turned off for this project
|
|
290
|
+
# (or an ancestor) after the link was minted — stop resolving it.
|
|
291
|
+
#
|
|
292
|
+
# 410, not 404 (#2697): this is the same "intentionally gone" family as
|
|
293
|
+
# revocation, expiry and Trash above — the link was real and an admin
|
|
294
|
+
# deliberately withdrew it. 404 would tell the recipient "no such thing",
|
|
295
|
+
# which is the wrong thing to tell someone holding a link that worked
|
|
296
|
+
# yesterday, and it contradicted the workspace toggle's own help copy.
|
|
297
|
+
#
|
|
298
|
+
# It reveals nothing the sibling branches do not: reaching this line
|
|
299
|
+
# means the token already resolved, so the recipient demonstrably held a
|
|
300
|
+
# valid link to this project. The instance kill switch above stays 404
|
|
301
|
+
# because it answers *before* resolution and cannot distinguish a real
|
|
302
|
+
# token from a forged one.
|
|
303
|
+
return Response(
|
|
304
|
+
{"detail": "Public sharing has been turned off for this project."},
|
|
305
|
+
status=status.HTTP_410_GONE,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
payload = share_services.get_cached_public_payload(link, serialize)
|
|
309
|
+
body = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
|
310
|
+
etag = 'W/"' + hashlib.sha256(body.encode()).hexdigest()[:32] + '"'
|
|
311
|
+
|
|
312
|
+
if request.headers.get("If-None-Match") == etag:
|
|
313
|
+
resp = Response(status=status.HTTP_304_NOT_MODIFIED)
|
|
314
|
+
else:
|
|
315
|
+
resp = Response(payload)
|
|
316
|
+
# Count a fresh delivery only; 304 re-polls of unchanged content don't
|
|
317
|
+
# inflate the "viewed N times" meter.
|
|
318
|
+
share_services.record_access(link)
|
|
319
|
+
resp["ETag"] = etag
|
|
320
|
+
# `private` (not `public`): a revoked link must stop resolving promptly, so a
|
|
321
|
+
# shared/CDN cache must never serve a since-revoked snapshot to another viewer.
|
|
322
|
+
# The per-viewer browser cache + ETag still make re-polls cheap.
|
|
323
|
+
resp["Cache-Control"] = "private, max-age=30"
|
|
324
|
+
# The token is a capability URL; keep it out of any Referer sent from the
|
|
325
|
+
# public page's subresource/outbound requests (defense-in-depth alongside the
|
|
326
|
+
# page's own <meta name="referrer">).
|
|
327
|
+
resp["Referrer-Policy"] = "no-referrer"
|
|
328
|
+
return resp
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class PublicBoardShareView(APIView):
|
|
332
|
+
"""GET ``/api/v1/share/board/{token}/`` — public, unauthenticated, read-only.
|
|
333
|
+
|
|
334
|
+
Returns a minimized board snapshot. ``410`` for a revoked or expired link or a
|
|
335
|
+
Trashed project, ``404`` for an
|
|
336
|
+
unknown/invalid token or when the instance kill switch is off (uniform 404 hides
|
|
337
|
+
whether the feature or a given link exists). A weak ``ETag`` + short
|
|
338
|
+
``Cache-Control`` lets embeds re-poll cheaply.
|
|
339
|
+
"""
|
|
340
|
+
|
|
341
|
+
permission_classes = [AllowAny] # noqa: RUF012
|
|
342
|
+
authentication_classes: list[Any] = [] # noqa: RUF012
|
|
343
|
+
throttle_classes = [ShareLinkAccessThrottle] # noqa: RUF012
|
|
344
|
+
http_method_names = ["get"] # noqa: RUF012
|
|
345
|
+
|
|
346
|
+
@extend_schema(
|
|
347
|
+
summary="Public read-only board snapshot for a share token",
|
|
348
|
+
responses={
|
|
349
|
+
200: OpenApiResponse(
|
|
350
|
+
description="Minimized board snapshot (columns + whitelisted cards)."
|
|
351
|
+
),
|
|
352
|
+
404: OpenApiResponse(description="Unknown/invalid token, or sharing disabled."),
|
|
353
|
+
410: OpenApiResponse(
|
|
354
|
+
description="This share link has been revoked or has expired, "
|
|
355
|
+
"or the project was moved to Trash."
|
|
356
|
+
),
|
|
357
|
+
},
|
|
358
|
+
)
|
|
359
|
+
def get(self, request: Request, token: str) -> Response:
|
|
360
|
+
return _serve_public_share(
|
|
361
|
+
request, token, ShareContentKind.BOARD, share_services.serialize_public_board
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class PublicScheduleShareView(APIView):
|
|
366
|
+
"""GET ``/api/v1/share/schedule/{token}/`` — public, unauthenticated, read-only.
|
|
367
|
+
|
|
368
|
+
Sibling of :class:`PublicBoardShareView` for the schedule/Gantt projection
|
|
369
|
+
(#1486, ADR-0265). Same security envelope; only the resolved ``content_kind``
|
|
370
|
+
and the projection differ. A board token can never resolve here — the
|
|
371
|
+
``content_kind`` discriminator in ``resolve_share_link`` returns ``None`` → 404.
|
|
372
|
+
"""
|
|
373
|
+
|
|
374
|
+
permission_classes = [AllowAny] # noqa: RUF012
|
|
375
|
+
authentication_classes: list[Any] = [] # noqa: RUF012
|
|
376
|
+
throttle_classes = [ShareLinkAccessThrottle] # noqa: RUF012
|
|
377
|
+
http_method_names = ["get"] # noqa: RUF012
|
|
378
|
+
|
|
379
|
+
@extend_schema(
|
|
380
|
+
summary="Public read-only schedule snapshot for a share token",
|
|
381
|
+
responses={
|
|
382
|
+
200: OpenApiResponse(
|
|
383
|
+
description="Minimized schedule snapshot (tasks + dependency edges)."
|
|
384
|
+
),
|
|
385
|
+
404: OpenApiResponse(description="Unknown/invalid token, or sharing disabled."),
|
|
386
|
+
410: OpenApiResponse(
|
|
387
|
+
description="This share link has been revoked or has expired, "
|
|
388
|
+
"or the project was moved to Trash."
|
|
389
|
+
),
|
|
390
|
+
},
|
|
391
|
+
)
|
|
392
|
+
def get(self, request: Request, token: str) -> Response:
|
|
393
|
+
return _serve_public_share(
|
|
394
|
+
request, token, ShareContentKind.SCHEDULE, share_services.serialize_public_schedule
|
|
395
|
+
)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Workspace → Program → Project sharing-settings resolution (ADR-0135, #978).
|
|
2
|
+
|
|
3
|
+
``public_sharing`` (anyone with the link can view, no sign-in) and ``allow_guests``
|
|
4
|
+
(external collaborators) are set at the workspace by default and may be overridden
|
|
5
|
+
per program/project. The *effective* value is resolved here, computed-on-read
|
|
6
|
+
(ADR-0108) — there is no stored/denormalized effective column to keep in sync.
|
|
7
|
+
Clients (web, mobile, MCP) read the serializer's ``effective_public_sharing`` /
|
|
8
|
+
``effective_allow_guests``; they never re-implement this precedence.
|
|
9
|
+
|
|
10
|
+
Precedence: project override → program override → workspace value.
|
|
11
|
+
``inherited_*`` answers "what would I get if this scope's override were cleared?"
|
|
12
|
+
(i.e. the parent's effective value) and drives the settings "Inherit (On/Off)"
|
|
13
|
+
affordance.
|
|
14
|
+
|
|
15
|
+
``ENFORCE`` (a workspace admin locking sharing so lower scopes cannot *loosen* it)
|
|
16
|
+
is an Enterprise capability. OSS ships the neutral hook below and registers no
|
|
17
|
+
provider, so ``ENFORCE`` degrades to ``SUGGEST`` (no lock) — a program/project may
|
|
18
|
+
freely loosen or tighten. trueppm-enterprise registers a provider in its
|
|
19
|
+
``AppConfig.ready()`` — the integrations-registry idiom (ADR-0029/0049), mirroring
|
|
20
|
+
``iteration_label.register_terminology_enforcement_provider``. OSS never imports
|
|
21
|
+
enterprise.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
from typing import TYPE_CHECKING
|
|
28
|
+
|
|
29
|
+
from django.conf import settings
|
|
30
|
+
|
|
31
|
+
from trueppm_api.core.extension_providers import guard_single_provider
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
35
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
36
|
+
|
|
37
|
+
#: The two inheritable sharing booleans. The field name is identical on
|
|
38
|
+
#: Workspace (non-null root), Program, and Project (nullable override), so the
|
|
39
|
+
#: resolver below is field-agnostic.
|
|
40
|
+
SHARING_FIELDS = ("public_sharing", "allow_guests")
|
|
41
|
+
|
|
42
|
+
# Enterprise registers a zero-arg predicate that returns True when sharing
|
|
43
|
+
# enforcement is licensed/active. OSS leaves it None → enforcement inactive, so
|
|
44
|
+
# ENFORCE behaves as SUGGEST (no lock).
|
|
45
|
+
_ENFORCEMENT_PROVIDER: Callable[[], bool] | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def register_sharing_enforcement_provider(provider: Callable[[], bool] | None) -> None:
|
|
49
|
+
"""Register (or clear) the sharing-enforcement provider. Enterprise calls this."""
|
|
50
|
+
global _ENFORCEMENT_PROVIDER
|
|
51
|
+
_ENFORCEMENT_PROVIDER = guard_single_provider(
|
|
52
|
+
_ENFORCEMENT_PROVIDER, provider, "sharing enforcement"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def sharing_enforcement_active() -> bool:
|
|
57
|
+
"""True only when an enterprise provider is registered AND reports active.
|
|
58
|
+
|
|
59
|
+
OSS has no provider → always False, so a workspace ``ENFORCE`` policy never
|
|
60
|
+
locks downstream sharing overrides in the community edition.
|
|
61
|
+
"""
|
|
62
|
+
return _ENFORCEMENT_PROVIDER is not None and bool(_ENFORCEMENT_PROVIDER())
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def public_sharing_instance_enabled() -> bool:
|
|
66
|
+
"""Operator-level kill switch (ADR-0245) — public sharing org-wide, on or off.
|
|
67
|
+
|
|
68
|
+
This is a strictly *coarser* gate than :func:`resolve_effective_sharing`: it
|
|
69
|
+
answers "does this instance serve public links at all", before any
|
|
70
|
+
workspace/program/project policy is consulted. The share views check it first
|
|
71
|
+
and answer 404 (not 410) because it fires before token resolution and so
|
|
72
|
+
cannot distinguish a real token from a forged one.
|
|
73
|
+
|
|
74
|
+
It lives here rather than in the views because it is half of the answer to
|
|
75
|
+
"will a link I mint actually resolve?", and non-view callers need to ask that
|
|
76
|
+
question too — ``create_demo_share_link`` does, so it can refuse to print a
|
|
77
|
+
URL the instance will never serve (#2781).
|
|
78
|
+
"""
|
|
79
|
+
return bool(getattr(settings, "TRUEPPM_PUBLIC_BOARD_SHARING_ENABLED", True))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def default_share_link_expiry_days() -> int:
|
|
83
|
+
"""Days a minted link lasts when the caller does not say (#3177).
|
|
84
|
+
|
|
85
|
+
Applies only when ``expires_at`` is **absent** from the request. An explicit
|
|
86
|
+
``null`` still mints a link that never expires — a standing wallboard or embed is
|
|
87
|
+
a real use case and this is a default, not a ceiling.
|
|
88
|
+
|
|
89
|
+
Why a default at all: the share *serve* path is thorough — it re-checks the kill
|
|
90
|
+
switch, revocation, expiry, project Trash and the ADR-0135 policy on every read —
|
|
91
|
+
but all of that is someone remembering to act. A link nobody revokes serves
|
|
92
|
+
delivery status to whoever still holds the URL, including after they leave. The
|
|
93
|
+
web dialog already prefills a 30-day nudge, so this closes the gap for callers
|
|
94
|
+
that are not the dialog: agent tokens, MCP clients, integrators and scripts, which
|
|
95
|
+
previously minted a permanent link by saying nothing.
|
|
96
|
+
|
|
97
|
+
``0`` or a negative value restores the pre-#3177 behavior (omitted means never)
|
|
98
|
+
for an operator who wants it.
|
|
99
|
+
"""
|
|
100
|
+
return int(getattr(settings, "TRUEPPM_SHARE_LINK_DEFAULT_EXPIRY_DAYS", 90))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _enforced(workspace: Workspace) -> bool:
|
|
104
|
+
"""Whether the workspace value is a hard ceiling (Enterprise lock active)."""
|
|
105
|
+
from trueppm_api.apps.workspace.models import TermOverridePolicy
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
workspace.public_sharing_override_policy == TermOverridePolicy.ENFORCE
|
|
109
|
+
and sharing_enforcement_active()
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def resolve_effective_sharing(
|
|
114
|
+
obj: Program | Project,
|
|
115
|
+
field: str,
|
|
116
|
+
*,
|
|
117
|
+
workspace: Workspace | None = None,
|
|
118
|
+
) -> bool:
|
|
119
|
+
"""Resolve the effective value of ``field`` for a program or project.
|
|
120
|
+
|
|
121
|
+
``workspace`` may be passed to avoid re-loading the singleton per object when
|
|
122
|
+
resolving a list (the serializer caches it once); otherwise it is loaded here.
|
|
123
|
+
"""
|
|
124
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
125
|
+
|
|
126
|
+
if workspace is None:
|
|
127
|
+
workspace = Workspace.load()
|
|
128
|
+
|
|
129
|
+
ws_value = bool(getattr(workspace, field))
|
|
130
|
+
if _enforced(workspace):
|
|
131
|
+
# Enterprise lock: the workspace value wins regardless of lower overrides.
|
|
132
|
+
return ws_value
|
|
133
|
+
|
|
134
|
+
own = getattr(obj, field) # nullable override on Program/Project
|
|
135
|
+
if own is not None:
|
|
136
|
+
return bool(own)
|
|
137
|
+
return _parent_value(obj, field, workspace=workspace)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def resolve_inherited_sharing(
|
|
141
|
+
obj: Program | Project,
|
|
142
|
+
field: str,
|
|
143
|
+
*,
|
|
144
|
+
workspace: Workspace | None = None,
|
|
145
|
+
) -> bool:
|
|
146
|
+
"""Value ``obj`` would inherit if its own override were cleared.
|
|
147
|
+
|
|
148
|
+
Drives the settings "Inherit (On/Off)" affordance — it deliberately skips
|
|
149
|
+
``obj``'s own override and resolves from the parent up. Under an active
|
|
150
|
+
Enterprise lock the inherited value is the (ceiling) workspace value.
|
|
151
|
+
"""
|
|
152
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
153
|
+
|
|
154
|
+
if workspace is None:
|
|
155
|
+
workspace = Workspace.load()
|
|
156
|
+
|
|
157
|
+
if _enforced(workspace):
|
|
158
|
+
return bool(getattr(workspace, field))
|
|
159
|
+
return _parent_value(obj, field, workspace=workspace)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _parent_value(
|
|
163
|
+
obj: Program | Project,
|
|
164
|
+
field: str,
|
|
165
|
+
*,
|
|
166
|
+
workspace: Workspace,
|
|
167
|
+
) -> bool:
|
|
168
|
+
"""Resolve ``field`` from ``obj``'s parent scope up to the workspace.
|
|
169
|
+
|
|
170
|
+
A Program's parent is the workspace. A Project's parent is its program (whose
|
|
171
|
+
own override resolves up to the workspace) or, if standalone, the workspace.
|
|
172
|
+
"""
|
|
173
|
+
# Local import keeps this module import-safe from migrations.
|
|
174
|
+
from trueppm_api.apps.projects.models import Program
|
|
175
|
+
|
|
176
|
+
# A Program's parent is the workspace (no intermediate scope); a Project's
|
|
177
|
+
# parent is its program, if it has one.
|
|
178
|
+
program: Program | None = None
|
|
179
|
+
if not isinstance(obj, Program) and obj.program_id:
|
|
180
|
+
program = obj.program
|
|
181
|
+
|
|
182
|
+
if program is not None:
|
|
183
|
+
program_override = getattr(program, field)
|
|
184
|
+
if program_override is not None:
|
|
185
|
+
return bool(program_override)
|
|
186
|
+
|
|
187
|
+
return bool(getattr(workspace, field))
|