trueppm-api 0.4.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
"""Workspace-level models — singleton config, membership, invites, groups (ADR-0087).
|
|
2
|
+
|
|
3
|
+
The *workspace* is the whole self-hosted installation (single-tenant; multi-tenancy
|
|
4
|
+
is Enterprise). It is introduced here for the first time to back the Workspace
|
|
5
|
+
settings pages (#517 General, #518 Members, #519 Groups & teams).
|
|
6
|
+
|
|
7
|
+
Key design points (see ADR-0087):
|
|
8
|
+
- ``Workspace`` is a singleton config row (``singleton_key=1``), lazily materialized
|
|
9
|
+
on first access — no data migration. It does NOT extend ``VersionedModel`` (config
|
|
10
|
+
is not a mobile-offline sync entity).
|
|
11
|
+
- ``WorkspaceRole`` is deliberately separate from the project-scoped ``Role``
|
|
12
|
+
(#518): workspace permission tiers are coarser than project scheduling roles.
|
|
13
|
+
- ``WorkspaceMembership``/``Group``/``GroupMembership`` extend ``VersionedModel`` so
|
|
14
|
+
they participate in the offline sync protocol and support direct permission
|
|
15
|
+
queries — mirroring the ``ProjectMembership`` pattern (ADR-0070).
|
|
16
|
+
- A ``Group`` confers project access: ``GroupProject`` links a group to a project
|
|
17
|
+
with a granted project ``Role``; membership is reconciled into ``ProjectMembership``
|
|
18
|
+
rows by ``services.reconcile_group_access`` (the cascade).
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import datetime
|
|
24
|
+
import uuid
|
|
25
|
+
|
|
26
|
+
from django.conf import settings
|
|
27
|
+
from django.contrib.postgres.fields import ArrayField
|
|
28
|
+
from django.core.validators import MaxValueValidator, MinValueValidator
|
|
29
|
+
from django.db import models
|
|
30
|
+
from django.utils import timezone
|
|
31
|
+
from simple_history.models import HistoricalRecords
|
|
32
|
+
|
|
33
|
+
from trueppm_api.apps.access.models import Role
|
|
34
|
+
from trueppm_api.apps.projects.models import (
|
|
35
|
+
DurationChangePercentPolicy,
|
|
36
|
+
EstimationScale,
|
|
37
|
+
Methodology,
|
|
38
|
+
VersionedModel,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# MCAttributionAudience lives in scheduling.models (the gate consumer); imported
|
|
42
|
+
# here only as a choices set for the column. scheduling never imports workspace,
|
|
43
|
+
# so there is no cycle (ADR-0144).
|
|
44
|
+
from trueppm_api.apps.scheduling.models import MCAttributionAudience
|
|
45
|
+
|
|
46
|
+
INVITE_TTL_DAYS = 7 # ADR-0087 §4 — invite token validity window
|
|
47
|
+
|
|
48
|
+
# English month names, indexed 1–12, for the fiscal-year display label (#756).
|
|
49
|
+
# Hard-coded rather than derived from ``calendar``/``strftime`` because those are
|
|
50
|
+
# locale-sensitive and the workspace anchor must read identically regardless of
|
|
51
|
+
# the server's LC_TIME (UI copy is US-English per project convention).
|
|
52
|
+
_MONTH_NAMES = (
|
|
53
|
+
"",
|
|
54
|
+
"January",
|
|
55
|
+
"February",
|
|
56
|
+
"March",
|
|
57
|
+
"April",
|
|
58
|
+
"May",
|
|
59
|
+
"June",
|
|
60
|
+
"July",
|
|
61
|
+
"August",
|
|
62
|
+
"September",
|
|
63
|
+
"October",
|
|
64
|
+
"November",
|
|
65
|
+
"December",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class WorkspaceRole(models.IntegerChoices):
|
|
70
|
+
"""Workspace-level permission tiers, separate from the project ``Role`` (#518).
|
|
71
|
+
|
|
72
|
+
Ordinals follow the ADR-0072 100-unit banding so Enterprise can register
|
|
73
|
+
intermediate roles (e.g. a billing-admin at 250) without an OSS renumber.
|
|
74
|
+
Write access gates on ``role >= WorkspaceRole.ADMIN``; OWNER carries the
|
|
75
|
+
last-owner guard (a workspace must never be left without an owner).
|
|
76
|
+
|
|
77
|
+
Account *lifecycle* (active/guest/deactivated) is the orthogonal
|
|
78
|
+
``WorkspaceMembership.status`` field — a "guest" is a permission-limited
|
|
79
|
+
external collaborator, not a separate role tier.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
MEMBER = 100, "Member"
|
|
83
|
+
ADMIN = 300, "Admin"
|
|
84
|
+
OWNER = 400, "Owner"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MemberStatus(models.TextChoices):
|
|
88
|
+
"""Account lifecycle for a workspace member (orthogonal to role)."""
|
|
89
|
+
|
|
90
|
+
ACTIVE = "active", "Active"
|
|
91
|
+
GUEST = "guest", "Guest"
|
|
92
|
+
DEACTIVATED = "deactivated", "Deactivated"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class InviteStatus(models.TextChoices):
|
|
96
|
+
PENDING = "pending", "Pending"
|
|
97
|
+
ACCEPTED = "accepted", "Accepted"
|
|
98
|
+
REVOKED = "revoked", "Revoked"
|
|
99
|
+
EXPIRED = "expired", "Expired"
|
|
100
|
+
# Terminal email-delivery failure (retries exhausted). Distinct from EXPIRED
|
|
101
|
+
# (token lifetime lapsed): the raw token is cleared regardless so it cannot
|
|
102
|
+
# linger at rest after the send path gives up. An admin can re-invite.
|
|
103
|
+
FAILED = "failed", "Failed"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _default_work_week() -> list[bool]:
|
|
107
|
+
"""Mon–Sun working-day flags; default Mon–Fri working, weekend off.
|
|
108
|
+
|
|
109
|
+
A callable default is required so each row gets its own list rather than
|
|
110
|
+
sharing one mutable instance across rows.
|
|
111
|
+
"""
|
|
112
|
+
return [True, True, True, True, True, False, False]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _default_allowed_attachment_types() -> list[str]:
|
|
116
|
+
"""Workspace seed allow-list — the system default (ADR-0153, #976).
|
|
117
|
+
|
|
118
|
+
Sourced from the single canonical set so the column default and the
|
|
119
|
+
historic hardcoded gate cannot drift. A callable default (not a literal)
|
|
120
|
+
keeps the migration state stable and gives each row its own list; sorted so
|
|
121
|
+
the ArrayField has a deterministic order.
|
|
122
|
+
"""
|
|
123
|
+
from trueppm_api.apps.projects.attachment_policy import (
|
|
124
|
+
SYSTEM_DEFAULT_ATTACHMENT_TYPES,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return sorted(SYSTEM_DEFAULT_ATTACHMENT_TYPES)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _workspace_logo_upload_to(instance: Workspace, filename: str) -> str:
|
|
131
|
+
"""Storage key for an uploaded workspace logo (ADR-0149).
|
|
132
|
+
|
|
133
|
+
A fresh UUID prefix per upload means a *Replace* never overwrites the prior
|
|
134
|
+
file in place — the old blob is deleted explicitly on commit (avoiding a
|
|
135
|
+
storage race) and the new URL cache-busts for free. Mirrors the
|
|
136
|
+
``TaskAttachment`` ``upload_to`` precedent (ADR-0075).
|
|
137
|
+
"""
|
|
138
|
+
suffix = ".webp" if filename.lower().endswith(".webp") else ".png"
|
|
139
|
+
# Stored under the shared local-dev branding root (gitignored, like
|
|
140
|
+
# ``attachments/`` and ``media/``); prod points STORAGES at object storage.
|
|
141
|
+
return f"branding/workspace-logo/{uuid.uuid4().hex}{suffix}"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class TermOverridePolicy(models.TextChoices):
|
|
145
|
+
"""How a workspace terminology default cascades to programs/projects (ADR-0116).
|
|
146
|
+
|
|
147
|
+
OSS honors ``INHERIT`` and ``SUGGEST`` identically as "lower levels may
|
|
148
|
+
override" — they differ only in whether the workspace default pre-fills a new
|
|
149
|
+
project's create form. ``ENFORCE`` (locking the term so a program/project
|
|
150
|
+
cannot override it) is an Enterprise capability: in the community edition it
|
|
151
|
+
degrades to ``SUGGEST`` (no-op) unless a terminology-enforcement provider is
|
|
152
|
+
registered (``apps.projects.iteration_label``, ``trueppm-enterprise#154``).
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
INHERIT = "inherit", "Inherit"
|
|
156
|
+
SUGGEST = "suggest", "Suggest"
|
|
157
|
+
ENFORCE = "enforce", "Enforce"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Workspace(models.Model):
|
|
161
|
+
"""Singleton installation-wide configuration (#517).
|
|
162
|
+
|
|
163
|
+
Enforced single-row via the unique ``singleton_key`` (ADR-0081 pattern) and
|
|
164
|
+
lazily created on first GET via :meth:`load` (ADR-0079 ``PhaseGateConfig``
|
|
165
|
+
precedent) — there is intentionally no data migration to seed it.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
169
|
+
# Always 1 — the unique constraint makes a second row impossible.
|
|
170
|
+
singleton_key = models.PositiveSmallIntegerField(default=1, editable=False, unique=True)
|
|
171
|
+
|
|
172
|
+
name = models.CharField(max_length=255, default="TruePPM Workspace")
|
|
173
|
+
# Read-only via the API. Self-hosted installs have no subdomain; the column
|
|
174
|
+
# exists so a future hosted edition can populate it without a schema change.
|
|
175
|
+
subdomain = models.CharField(max_length=63, blank=True, default="")
|
|
176
|
+
# Fallback IANA timezone for a project that sets none. Its single reader is
|
|
177
|
+
# ``notifications.services._project_timezone`` (#3377), which interprets a
|
|
178
|
+
# project's quiet-hours window in it — Project.timezone wins where set, and
|
|
179
|
+
# this value is the next link before ``settings.TIME_ZONE``. Not a display
|
|
180
|
+
# timezone: an instant shown to a person is re-clocked from that person's
|
|
181
|
+
# own ``profiles.Profile.timezone``.
|
|
182
|
+
timezone = models.CharField(max_length=64, default="UTC")
|
|
183
|
+
# Structured fiscal-year anchor (#756): month (1–12) + day (1–31), replacing
|
|
184
|
+
# the former free-text ``fiscal_year_start`` CharField. Year-agnostic — a
|
|
185
|
+
# workspace whose FY starts April 1 stores (4, 1). Drives quarter labels and
|
|
186
|
+
# boundaries across the workspace, including the Schedule timeline (#755).
|
|
187
|
+
# Day-vs-month validity (no Feb 30) is enforced in the settings serializer,
|
|
188
|
+
# which is the only write path; the model carries the coarse range validators.
|
|
189
|
+
fiscal_year_start_month = models.PositiveSmallIntegerField(
|
|
190
|
+
default=1,
|
|
191
|
+
validators=[MinValueValidator(1), MaxValueValidator(12)],
|
|
192
|
+
)
|
|
193
|
+
fiscal_year_start_day = models.PositiveSmallIntegerField(
|
|
194
|
+
default=1,
|
|
195
|
+
validators=[MinValueValidator(1), MaxValueValidator(31)],
|
|
196
|
+
)
|
|
197
|
+
work_week = ArrayField(models.BooleanField(), size=7, default=_default_work_week)
|
|
198
|
+
default_project_view = models.CharField(max_length=32, default="board")
|
|
199
|
+
allow_guests = models.BooleanField(default=True)
|
|
200
|
+
public_sharing = models.BooleanField(default=False)
|
|
201
|
+
# Iteration-container label default for the whole workspace (ADR-0116, #1106) —
|
|
202
|
+
# the non-null root of the Workspace → Program → Project inheritance chain. A
|
|
203
|
+
# program/project whose own override is NULL resolves up to this value.
|
|
204
|
+
# Display-only (ADR-0038/0111); never gates tabs, routes, or CPM.
|
|
205
|
+
iteration_label = models.CharField(max_length=32, default="Sprint")
|
|
206
|
+
# INHERIT/SUGGEST (OSS) allow lower-level overrides; ENFORCE (Enterprise) locks
|
|
207
|
+
# the term downstream and is a no-op in the community edition (ADR-0116).
|
|
208
|
+
iteration_label_override_policy = models.CharField(
|
|
209
|
+
max_length=16,
|
|
210
|
+
choices=TermOverridePolicy.choices,
|
|
211
|
+
default=TermOverridePolicy.SUGGEST,
|
|
212
|
+
)
|
|
213
|
+
# Governs whether a program/project may override the workspace sharing
|
|
214
|
+
# settings (public_sharing / allow_guests) — ADR-0135, #978. SUGGEST (OSS
|
|
215
|
+
# default) lets lower scopes loosen *or* tighten freely; ENFORCE (Enterprise)
|
|
216
|
+
# makes the workspace value a hard ceiling so downstream cannot loosen it.
|
|
217
|
+
# ENFORCE is a no-op in the community edition (degrades to SUGGEST) unless a
|
|
218
|
+
# sharing-enforcement provider is registered (``apps.projects.sharing_settings``).
|
|
219
|
+
public_sharing_override_policy = models.CharField(
|
|
220
|
+
max_length=16,
|
|
221
|
+
choices=TermOverridePolicy.choices,
|
|
222
|
+
default=TermOverridePolicy.SUGGEST,
|
|
223
|
+
)
|
|
224
|
+
# Per-workspace Monte Carlo forecast-history config (ADR-0144, #1232) — the
|
|
225
|
+
# non-null root of the Workspace → Program → Project inheritance chain. A
|
|
226
|
+
# program/project whose own override is NULL resolves up to these values via
|
|
227
|
+
# ``scheduling.forecast_history_settings``. All three carry model-level defaults
|
|
228
|
+
# that reproduce the pre-0143 behavior exactly (history on, cap 100, attribution
|
|
229
|
+
# to Admin/Owner), so the migration is purely additive.
|
|
230
|
+
mc_history_enabled = models.BooleanField(default=True)
|
|
231
|
+
# Default matches settings.MC_HISTORY_CAP; the resolver clamps reads to
|
|
232
|
+
# settings.MC_HISTORY_HARD_CAP (500) so a misconfigured value can't unbound the
|
|
233
|
+
# nightly purge.
|
|
234
|
+
mc_history_retention_cap = models.PositiveIntegerField(default=100)
|
|
235
|
+
mc_history_attribution_audience = models.CharField(
|
|
236
|
+
max_length=16,
|
|
237
|
+
choices=MCAttributionAudience.choices,
|
|
238
|
+
default=MCAttributionAudience.ADMIN_OWNER,
|
|
239
|
+
)
|
|
240
|
+
# Whether programs/projects may override the forecast-history config above
|
|
241
|
+
# (ADR-0144, mirroring public_sharing_override_policy). SUGGEST (OSS default) =
|
|
242
|
+
# downstream may override freely; ENFORCE = Enterprise hard lock (no-op in OSS —
|
|
243
|
+
# stored but never enforced; the enforcement seam lives in
|
|
244
|
+
# ``scheduling.forecast_history_settings``).
|
|
245
|
+
mc_history_override_policy = models.CharField(
|
|
246
|
+
max_length=16,
|
|
247
|
+
choices=TermOverridePolicy.choices,
|
|
248
|
+
default=TermOverridePolicy.SUGGEST,
|
|
249
|
+
)
|
|
250
|
+
# Workspace-wide default planning methodology (ADR-0107, issue 955) — the
|
|
251
|
+
# non-null root of the Workspace → Program → Project methodology inheritance
|
|
252
|
+
# chain (the "experience preset"). New projects pre-fill from this default;
|
|
253
|
+
# the *effective* methodology a project displays is resolved computed-on-read
|
|
254
|
+
# in ``apps.projects.methodology`` (project ?? program ?? workspace, gated by
|
|
255
|
+
# the policy below). Default HYBRID mirrors Project/Program (ADR-0041), so the
|
|
256
|
+
# migration is purely additive — every existing project keeps its own value.
|
|
257
|
+
methodology = models.CharField(
|
|
258
|
+
max_length=16,
|
|
259
|
+
choices=Methodology.choices,
|
|
260
|
+
default=Methodology.HYBRID,
|
|
261
|
+
)
|
|
262
|
+
# How the workspace default cascades to programs/projects (ADR-0107). Unlike
|
|
263
|
+
# iteration_label/sharing, methodology is NOT-NULL on every scope (no null
|
|
264
|
+
# "inherit" sentinel), so inheritance is POLICY-driven, not override-presence
|
|
265
|
+
# driven: INHERIT/active-ENFORCE → the workspace default wins and the
|
|
266
|
+
# per-scope picker is read-only; SUGGEST → each scope's own methodology is
|
|
267
|
+
# honored (default — preserves today's behavior where each project owns its
|
|
268
|
+
# methodology). ENFORCE is the Enterprise lock seam (trueppm-enterprise#144);
|
|
269
|
+
# OSS registers no enforcement provider, so ENFORCE degrades to SUGGEST (the
|
|
270
|
+
# methodology PATCH is allowed and the per-scope override wins).
|
|
271
|
+
methodology_override_policy = models.CharField(
|
|
272
|
+
max_length=16,
|
|
273
|
+
choices=TermOverridePolicy.choices,
|
|
274
|
+
default=TermOverridePolicy.SUGGEST,
|
|
275
|
+
)
|
|
276
|
+
# Workspace-wide default for how percent_complete reacts to a task duration
|
|
277
|
+
# change (ADR-0151, #414) — the non-null root of the Workspace → Program →
|
|
278
|
+
# Project inheritance chain. Default KEEP reproduces today's behavior exactly
|
|
279
|
+
# (the PM-entered % is left untouched), so the migration is purely additive.
|
|
280
|
+
# Resolved computed-on-read in ``apps.projects.task_duration_settings``.
|
|
281
|
+
task_duration_change_percent_policy = models.CharField(
|
|
282
|
+
max_length=16,
|
|
283
|
+
choices=DurationChangePercentPolicy.choices,
|
|
284
|
+
default=DurationChangePercentPolicy.KEEP,
|
|
285
|
+
)
|
|
286
|
+
# Whether programs/projects may override the policy above (ADR-0151, mirroring
|
|
287
|
+
# public_sharing_override_policy). SUGGEST (OSS default) = downstream may
|
|
288
|
+
# override freely; ENFORCE = Enterprise hard lock (stored but never enforced in
|
|
289
|
+
# OSS — the enforcement seam lives in ``apps.projects.task_duration_settings``,
|
|
290
|
+
# which registers no provider in the community edition, so ENFORCE → SUGGEST).
|
|
291
|
+
task_duration_change_percent_override_policy = models.CharField(
|
|
292
|
+
max_length=16,
|
|
293
|
+
choices=TermOverridePolicy.choices,
|
|
294
|
+
default=TermOverridePolicy.SUGGEST,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
# Per-workspace estimation scale (ADR-0510, #2027) — the non-null root of the
|
|
298
|
+
# Workspace → Program → Project inheritance chain. Default FIBONACCI reproduces
|
|
299
|
+
# today's de-facto scale (the only hardcoded picker, StoryDetailDrawer, used
|
|
300
|
+
# Fibonacci), so the migration is purely additive. Resolved computed-on-read in
|
|
301
|
+
# ``apps.projects.estimation_scale``. There is NO override_policy — unlike the
|
|
302
|
+
# duration policy / calendar, the scale is a plain PO/team preference with no
|
|
303
|
+
# enforcement seam; it is freely overridable at every scope (OSS by
|
|
304
|
+
# construction, ADR-0510). Display/input-only: it never touches the stored
|
|
305
|
+
# integer ``story_points`` or any velocity/rollup math.
|
|
306
|
+
estimation_scale = models.CharField(
|
|
307
|
+
max_length=16,
|
|
308
|
+
choices=EstimationScale.choices,
|
|
309
|
+
default=EstimationScale.FIBONACCI,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# Per-workspace default for the sprint story picker's "Ready only" filter
|
|
313
|
+
# (ADR-0758, #2670) — the non-null root of the Workspace → Program → Project
|
|
314
|
+
# inheritance chain. Default True: a story marked below Ready (unestimated,
|
|
315
|
+
# missing/unmet acceptance criteria) is hidden from the picker's default view
|
|
316
|
+
# rather than offered for commit. Resolved computed-on-read in
|
|
317
|
+
# ``apps.projects.sprint_picker_settings``. Mirrors ``estimation_scale``: there
|
|
318
|
+
# is NO override_policy — this is advisory UI guidance, never a commit-time
|
|
319
|
+
# block (OSS by construction; a hard ENFORCE gate is out of scope, Enterprise).
|
|
320
|
+
sprint_picker_ready_only_default = models.BooleanField(default=True)
|
|
321
|
+
|
|
322
|
+
# Per-workspace attachment policy (ADR-0153, #976) — the non-null root of the
|
|
323
|
+
# Workspace → Program → Project inheritance chain. ``attachments_enabled``
|
|
324
|
+
# gates whether task FILE uploads are permitted (external links are a separate
|
|
325
|
+
# capability, unaffected). ``allowed_attachment_types`` is the MIME allow-list,
|
|
326
|
+
# seeded from the system default so the migration is purely additive and the
|
|
327
|
+
# pre-0150 behavior is reproduced exactly. Both resolve computed-on-read in
|
|
328
|
+
# ``apps.projects.attachment_policy``; clients read the serializer's
|
|
329
|
+
# ``effective_*``/``inherited_*`` fields. A non-overridable security denylist
|
|
330
|
+
# (text/html etc.) is subtracted on every read regardless of this value.
|
|
331
|
+
attachments_enabled = models.BooleanField(default=True)
|
|
332
|
+
allowed_attachment_types = ArrayField(
|
|
333
|
+
models.CharField(max_length=255),
|
|
334
|
+
default=_default_allowed_attachment_types,
|
|
335
|
+
)
|
|
336
|
+
# Whether programs/projects may override the attachment policy above
|
|
337
|
+
# (ADR-0153, mirroring public_sharing_override_policy). SUGGEST (OSS default) =
|
|
338
|
+
# downstream may narrow or widen freely; ENFORCE = Enterprise hard lock (no-op
|
|
339
|
+
# in OSS — stored but never enforced; the enforcement seam lives in
|
|
340
|
+
# ``apps.projects.attachment_policy``).
|
|
341
|
+
attachments_override_policy = models.CharField(
|
|
342
|
+
max_length=16,
|
|
343
|
+
choices=TermOverridePolicy.choices,
|
|
344
|
+
default=TermOverridePolicy.SUGGEST,
|
|
345
|
+
)
|
|
346
|
+
# Workspace-scope MCP/agent read consent (ADR-0678, #2482) — the non-null root
|
|
347
|
+
# of the Workspace → Program → Project chain. Default True preserves the
|
|
348
|
+
# pre-0678 behavior exactly (the instance switch, ADR-0497, remains the
|
|
349
|
+
# operator's lever).
|
|
350
|
+
#
|
|
351
|
+
# why no ``mcp_override_policy`` here, breaking symmetry with the four
|
|
352
|
+
# settings above: this cascade is RESTRICTIVE-ONLY (AND), not override. Any
|
|
353
|
+
# scope may deny for itself and everything beneath it; no scope may grant over
|
|
354
|
+
# another's denial. An ENFORCE lock would let a workspace admin force MCP back
|
|
355
|
+
# ON for a team, which is exactly the "consent in name only" objection (#2415)
|
|
356
|
+
# this control exists to answer. Turning it off org-wide — the only coherent
|
|
357
|
+
# governance lever — already works through the AND.
|
|
358
|
+
mcp_enabled = models.BooleanField(default=True)
|
|
359
|
+
|
|
360
|
+
# In-product feedback / report-a-bug link (#2392).
|
|
361
|
+
#
|
|
362
|
+
# A LINK, never a beacon: nothing is transmitted by rendering the control or
|
|
363
|
+
# by opening it. The client assembles a prefilled URL the user reads and
|
|
364
|
+
# edits before choosing to submit it, so a self-hosted (or air-gapped)
|
|
365
|
+
# instance never phones home as a side effect of the feature existing.
|
|
366
|
+
#
|
|
367
|
+
# `feedback_url` empty means "use the built-in public tracker" — storing the
|
|
368
|
+
# default here instead would freeze today's URL into every existing
|
|
369
|
+
# workspace row, so an operator who never touched the setting could not be
|
|
370
|
+
# moved off it by an upgrade.
|
|
371
|
+
feedback_enabled = models.BooleanField(default=True)
|
|
372
|
+
feedback_url = models.URLField(blank=True, default="")
|
|
373
|
+
|
|
374
|
+
# Workspace-wide default working calendar (ADR-0441, #1987) — the root of the
|
|
375
|
+
# Project → Program → Workspace → system-default inheritance chain. NULL = fall
|
|
376
|
+
# through to the system default (Mon-Fri/8h/UTC); we do not seed a system-default
|
|
377
|
+
# Calendar row, so the migration is purely additive and every existing project keeps
|
|
378
|
+
# its own behavior (a project with no calendar resolved to the system default before
|
|
379
|
+
# and still does when the workspace calendar is unset). Resolved computed-on-read in
|
|
380
|
+
# ``apps.projects.calendar_settings`` and fed to CPM via ``scheduling.calendars``.
|
|
381
|
+
# PROTECT so a Calendar still in use as the workspace default cannot be deleted.
|
|
382
|
+
calendar = models.ForeignKey(
|
|
383
|
+
"projects.Calendar",
|
|
384
|
+
on_delete=models.PROTECT,
|
|
385
|
+
related_name="+",
|
|
386
|
+
null=True,
|
|
387
|
+
blank=True,
|
|
388
|
+
)
|
|
389
|
+
# Whether programs/projects may override the workspace calendar above (ADR-0441,
|
|
390
|
+
# mirroring the other *_override_policy fields). SUGGEST (OSS default) = downstream
|
|
391
|
+
# may override freely; ENFORCE = Enterprise hard lock (no-op in OSS — stored but
|
|
392
|
+
# never enforced; the enforcement seam lives in ``apps.projects.calendar_settings``,
|
|
393
|
+
# which registers no provider in the community edition, so ENFORCE degrades to
|
|
394
|
+
# SUGGEST). INHERIT hides the per-scope override affordance entirely.
|
|
395
|
+
calendar_override_policy = models.CharField(
|
|
396
|
+
max_length=16,
|
|
397
|
+
choices=TermOverridePolicy.choices,
|
|
398
|
+
default=TermOverridePolicy.SUGGEST,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Workspace branding logo (ADR-0149, #969). Raster only (PNG/WebP) — SVG is
|
|
402
|
+
# rejected at the serializer because it can embed <script> and the logo is
|
|
403
|
+
# served from a public (AllowAny) endpoint. A plain FileField, not ImageField,
|
|
404
|
+
# so the app carries no Pillow dependency (matches the TaskAttachment precedent,
|
|
405
|
+
# ADR-0075); the validated content type is pinned in ``logo_mime`` so the serve
|
|
406
|
+
# endpoint sets Content-Type from a trusted source rather than sniffing on read.
|
|
407
|
+
logo = models.FileField(upload_to=_workspace_logo_upload_to, blank=True, default="")
|
|
408
|
+
logo_mime = models.CharField(max_length=64, blank=True, default="")
|
|
409
|
+
|
|
410
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
411
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
412
|
+
|
|
413
|
+
# Team-readable audit of singleton config changes (ADR-0107 §5). Workspace had
|
|
414
|
+
# no history table before; this captures who/when/old→new for the methodology
|
|
415
|
+
# default + override policy (and every other singleton field) via the existing
|
|
416
|
+
# ``history_record_created`` signal that enterprise consumes for retention.
|
|
417
|
+
history = HistoricalRecords()
|
|
418
|
+
|
|
419
|
+
class Meta:
|
|
420
|
+
db_table = "workspace_workspace"
|
|
421
|
+
|
|
422
|
+
def __str__(self) -> str:
|
|
423
|
+
return f"Workspace({self.name})"
|
|
424
|
+
|
|
425
|
+
@classmethod
|
|
426
|
+
def load(cls) -> Workspace:
|
|
427
|
+
"""Return the singleton, creating it on first access."""
|
|
428
|
+
obj, _ = cls.objects.get_or_create(singleton_key=1)
|
|
429
|
+
return obj
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def fiscal_year_start_display(self) -> str:
|
|
433
|
+
"""Human label for the fiscal-year anchor, e.g. ``"April 6"`` (#756).
|
|
434
|
+
|
|
435
|
+
Read-only convenience exposed on the settings serializer for back-compat
|
|
436
|
+
with any reader that previously consumed the free-text ``fiscal_year_start``.
|
|
437
|
+
"""
|
|
438
|
+
return f"{_MONTH_NAMES[self.fiscal_year_start_month]} {self.fiscal_year_start_day}"
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class WorkspaceMembership(VersionedModel):
|
|
442
|
+
"""A user's workspace-level role + account status (#518).
|
|
443
|
+
|
|
444
|
+
Standalone through-model (not an M2M ``through=``) so it participates in
|
|
445
|
+
offline sync and can be queried directly in permission checks — mirrors
|
|
446
|
+
``ProjectMembership`` (ADR-0070). A membership row is created lazily the
|
|
447
|
+
first time an admin sets a user's role/status; users without a row default
|
|
448
|
+
to OWNER if they are a Django superuser, else MEMBER (see
|
|
449
|
+
``permissions._workspace_membership_role``).
|
|
450
|
+
"""
|
|
451
|
+
|
|
452
|
+
workspace = models.ForeignKey(
|
|
453
|
+
Workspace,
|
|
454
|
+
on_delete=models.CASCADE,
|
|
455
|
+
related_name="memberships",
|
|
456
|
+
)
|
|
457
|
+
user = models.ForeignKey(
|
|
458
|
+
settings.AUTH_USER_MODEL,
|
|
459
|
+
on_delete=models.CASCADE,
|
|
460
|
+
related_name="workspace_memberships",
|
|
461
|
+
)
|
|
462
|
+
role = models.IntegerField(choices=WorkspaceRole.choices, default=WorkspaceRole.MEMBER)
|
|
463
|
+
status = models.CharField(
|
|
464
|
+
max_length=16, choices=MemberStatus.choices, default=MemberStatus.ACTIVE
|
|
465
|
+
)
|
|
466
|
+
# Mirrors ProjectMembership (#590): joined_at backfills via default=timezone.now;
|
|
467
|
+
# role_changed_at is NULL until an actual role change.
|
|
468
|
+
joined_at = models.DateTimeField(default=timezone.now, editable=False)
|
|
469
|
+
role_changed_at = models.DateTimeField(null=True, blank=True, editable=False)
|
|
470
|
+
# Resource-availability baseline (#542): the workspace-member denominator the
|
|
471
|
+
# project-level allocation model (#489) compares its summed per-project
|
|
472
|
+
# percentages against. Models the member not being fully available — parental
|
|
473
|
+
# leave (40%), a part-time contract (60%), a known side commitment. The
|
|
474
|
+
# effective_* bounds carve out a temporary baseline (e.g. a single quarter);
|
|
475
|
+
# both NULL means it applies indefinitely. default=100 backfills every
|
|
476
|
+
# existing row to "fully available" so the AddField migration is non-
|
|
477
|
+
# interactive and the historical behaviour (everyone at 100%) is preserved.
|
|
478
|
+
availability_percent = models.PositiveSmallIntegerField(
|
|
479
|
+
default=100,
|
|
480
|
+
validators=[MinValueValidator(0), MaxValueValidator(100)],
|
|
481
|
+
)
|
|
482
|
+
availability_effective_from = models.DateField(null=True, blank=True)
|
|
483
|
+
availability_effective_to = models.DateField(null=True, blank=True)
|
|
484
|
+
availability_notes = models.TextField(blank=True, default="")
|
|
485
|
+
|
|
486
|
+
class Meta:
|
|
487
|
+
db_table = "workspace_membership"
|
|
488
|
+
constraints = [
|
|
489
|
+
models.UniqueConstraint(
|
|
490
|
+
fields=["workspace", "user"], name="uniq_workspace_membership_workspace_user"
|
|
491
|
+
),
|
|
492
|
+
]
|
|
493
|
+
|
|
494
|
+
def __str__(self) -> str:
|
|
495
|
+
return f"{self.user} — {WorkspaceRole(self.role).label} ({self.status})"
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
class WorkspaceInvite(models.Model):
|
|
499
|
+
"""A pending email invitation to join the workspace (#518, ADR-0087 §4).
|
|
500
|
+
|
|
501
|
+
The raw token (``secrets.token_urlsafe``) is emailed in the accept link and
|
|
502
|
+
never stored — only its SHA-256 hash lives here, so a database leak cannot
|
|
503
|
+
be replayed into account takeover. Email delivery uses the transactional
|
|
504
|
+
outbox pattern (``email_pending`` drained by ``tasks.drain_invite_emails``),
|
|
505
|
+
mirroring ``Notification``.
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
509
|
+
workspace = models.ForeignKey(
|
|
510
|
+
Workspace,
|
|
511
|
+
on_delete=models.CASCADE,
|
|
512
|
+
related_name="invites",
|
|
513
|
+
)
|
|
514
|
+
email = models.EmailField()
|
|
515
|
+
role = models.IntegerField(choices=WorkspaceRole.choices, default=WorkspaceRole.MEMBER)
|
|
516
|
+
# SHA-256 of the raw token — the durable credential the accept endpoint
|
|
517
|
+
# authenticates against. The raw token is never stored long-term.
|
|
518
|
+
token_hash = models.CharField(max_length=64, unique=True, db_index=True)
|
|
519
|
+
# Transient raw token, populated at create time so the async email drain can
|
|
520
|
+
# build the accept link, then CLEARED on first successful send (and on
|
|
521
|
+
# accept). Bounds at-rest exposure to the orphan window; after send only the
|
|
522
|
+
# hash remains, so a later DB leak cannot be replayed.
|
|
523
|
+
email_token = models.CharField(max_length=64, blank=True, default="")
|
|
524
|
+
invited_by = models.ForeignKey(
|
|
525
|
+
settings.AUTH_USER_MODEL,
|
|
526
|
+
on_delete=models.SET_NULL,
|
|
527
|
+
null=True,
|
|
528
|
+
blank=True,
|
|
529
|
+
related_name="workspace_invites_sent",
|
|
530
|
+
)
|
|
531
|
+
status = models.CharField(
|
|
532
|
+
max_length=16, choices=InviteStatus.choices, default=InviteStatus.PENDING
|
|
533
|
+
)
|
|
534
|
+
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
535
|
+
expires_at = models.DateTimeField()
|
|
536
|
+
accepted_at = models.DateTimeField(null=True, blank=True)
|
|
537
|
+
accepted_user = models.ForeignKey(
|
|
538
|
+
settings.AUTH_USER_MODEL,
|
|
539
|
+
on_delete=models.SET_NULL,
|
|
540
|
+
null=True,
|
|
541
|
+
blank=True,
|
|
542
|
+
related_name="workspace_invites_accepted",
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
# Email delivery state — drained by tasks.drain_invite_emails (ADR-0087 §Durable).
|
|
546
|
+
email_pending = models.BooleanField(default=False, db_index=True)
|
|
547
|
+
email_sent_at = models.DateTimeField(null=True, blank=True)
|
|
548
|
+
email_failed_at = models.DateTimeField(null=True, blank=True)
|
|
549
|
+
email_attempts = models.PositiveIntegerField(default=0)
|
|
550
|
+
|
|
551
|
+
class Meta:
|
|
552
|
+
db_table = "workspace_invite"
|
|
553
|
+
ordering = ["-created_at"]
|
|
554
|
+
indexes = [
|
|
555
|
+
models.Index(
|
|
556
|
+
fields=["email_pending", "-created_at"],
|
|
557
|
+
name="ix_invite_email_pending",
|
|
558
|
+
condition=models.Q(email_pending=True),
|
|
559
|
+
),
|
|
560
|
+
models.Index(fields=["status", "-created_at"], name="ix_invite_status"),
|
|
561
|
+
]
|
|
562
|
+
|
|
563
|
+
def __str__(self) -> str:
|
|
564
|
+
return f"WorkspaceInvite({self.email} → {WorkspaceRole(self.role).label}, {self.status})"
|
|
565
|
+
|
|
566
|
+
@staticmethod
|
|
567
|
+
def default_expiry() -> datetime.datetime:
|
|
568
|
+
return timezone.now() + datetime.timedelta(days=INVITE_TTL_DAYS)
|
|
569
|
+
|
|
570
|
+
@property
|
|
571
|
+
def is_expired(self) -> bool:
|
|
572
|
+
return timezone.now() >= self.expires_at
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class Group(VersionedModel):
|
|
576
|
+
"""A workspace team/group (#519).
|
|
577
|
+
|
|
578
|
+
Members are linked via the ``GroupMembership`` through-model; project
|
|
579
|
+
associations via ``GroupProject``. Adding a group to a project confers the
|
|
580
|
+
group's granted ``Role`` on every member by reconciling ``ProjectMembership``
|
|
581
|
+
rows (``services.reconcile_group_access``).
|
|
582
|
+
"""
|
|
583
|
+
|
|
584
|
+
workspace = models.ForeignKey(
|
|
585
|
+
Workspace,
|
|
586
|
+
on_delete=models.CASCADE,
|
|
587
|
+
related_name="groups",
|
|
588
|
+
)
|
|
589
|
+
name = models.CharField(max_length=120)
|
|
590
|
+
description = models.TextField(blank=True, default="")
|
|
591
|
+
lead = models.ForeignKey(
|
|
592
|
+
settings.AUTH_USER_MODEL,
|
|
593
|
+
on_delete=models.SET_NULL,
|
|
594
|
+
null=True,
|
|
595
|
+
blank=True,
|
|
596
|
+
related_name="groups_led",
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
class Meta:
|
|
600
|
+
db_table = "workspace_group"
|
|
601
|
+
ordering = ["name"]
|
|
602
|
+
|
|
603
|
+
def __str__(self) -> str:
|
|
604
|
+
return f"Group({self.name})"
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
class GroupMembership(VersionedModel):
|
|
608
|
+
"""Through-model linking a user to a :class:`Group` (#519)."""
|
|
609
|
+
|
|
610
|
+
group = models.ForeignKey(
|
|
611
|
+
Group,
|
|
612
|
+
on_delete=models.CASCADE,
|
|
613
|
+
related_name="memberships",
|
|
614
|
+
)
|
|
615
|
+
user = models.ForeignKey(
|
|
616
|
+
settings.AUTH_USER_MODEL,
|
|
617
|
+
on_delete=models.CASCADE,
|
|
618
|
+
related_name="group_memberships",
|
|
619
|
+
)
|
|
620
|
+
joined_at = models.DateTimeField(default=timezone.now, editable=False)
|
|
621
|
+
|
|
622
|
+
class Meta:
|
|
623
|
+
db_table = "workspace_group_membership"
|
|
624
|
+
constraints = [
|
|
625
|
+
models.UniqueConstraint(
|
|
626
|
+
fields=["group", "user"], name="uniq_group_membership_group_user"
|
|
627
|
+
),
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
def __str__(self) -> str:
|
|
631
|
+
return f"{self.user} ∈ {self.group}"
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
class GroupProject(models.Model):
|
|
635
|
+
"""Links a :class:`Group` to a project with the project ``Role`` it confers (#519).
|
|
636
|
+
|
|
637
|
+
Uses the project-scoped ``Role`` enum (not ``WorkspaceRole``) because the
|
|
638
|
+
cascade materializes ``ProjectMembership`` rows. The conferred role is
|
|
639
|
+
capped below OWNER by serializer validation — a group can never confer
|
|
640
|
+
project ownership (the last-owner guard must remain meaningful).
|
|
641
|
+
"""
|
|
642
|
+
|
|
643
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
644
|
+
group = models.ForeignKey(
|
|
645
|
+
Group,
|
|
646
|
+
on_delete=models.CASCADE,
|
|
647
|
+
related_name="project_links",
|
|
648
|
+
)
|
|
649
|
+
project = models.ForeignKey(
|
|
650
|
+
"projects.Project",
|
|
651
|
+
on_delete=models.CASCADE,
|
|
652
|
+
related_name="group_links",
|
|
653
|
+
)
|
|
654
|
+
role = models.IntegerField(choices=Role.choices, default=Role.MEMBER)
|
|
655
|
+
added_by = models.ForeignKey(
|
|
656
|
+
settings.AUTH_USER_MODEL,
|
|
657
|
+
on_delete=models.SET_NULL,
|
|
658
|
+
null=True,
|
|
659
|
+
blank=True,
|
|
660
|
+
related_name="group_projects_added",
|
|
661
|
+
)
|
|
662
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
663
|
+
|
|
664
|
+
class Meta:
|
|
665
|
+
db_table = "workspace_group_project"
|
|
666
|
+
constraints = [
|
|
667
|
+
models.UniqueConstraint(
|
|
668
|
+
fields=["group", "project"], name="uniq_group_project_group_project"
|
|
669
|
+
),
|
|
670
|
+
]
|
|
671
|
+
|
|
672
|
+
def __str__(self) -> str:
|
|
673
|
+
return f"{self.group} → {self.project_id} ({Role(self.role).label})"
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
class ExportJobStatus(models.TextChoices):
|
|
677
|
+
"""Lifecycle of an async workspace export (ADR-0174)."""
|
|
678
|
+
|
|
679
|
+
PENDING = "pending", "Pending"
|
|
680
|
+
RUNNING = "running", "Running"
|
|
681
|
+
SUCCESS = "success", "Success"
|
|
682
|
+
FAILED = "failed", "Failed"
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
class WorkspaceExportJob(models.Model):
|
|
686
|
+
"""Tracks a full-workspace archive export (ADR-0174, #641).
|
|
687
|
+
|
|
688
|
+
Plain (non-synced) model — an export job is server-side bookkeeping, never a
|
|
689
|
+
mobile-offline entity. There is no FK to ``Workspace`` because it is a
|
|
690
|
+
singleton; a job always concerns the one workspace. Mirrors ``taskruns.TaskRun``
|
|
691
|
+
for the status/timestamp shape. The drain re-dispatches ``pending`` rows whose
|
|
692
|
+
``celery_task_id`` never got set (broker down at ``on_commit``); the nightly
|
|
693
|
+
purge deletes rows past ``expires_at`` and their stored files.
|
|
694
|
+
"""
|
|
695
|
+
|
|
696
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
697
|
+
requested_by = models.ForeignKey(
|
|
698
|
+
settings.AUTH_USER_MODEL,
|
|
699
|
+
on_delete=models.SET_NULL,
|
|
700
|
+
null=True,
|
|
701
|
+
blank=True,
|
|
702
|
+
related_name="workspace_exports",
|
|
703
|
+
)
|
|
704
|
+
status = models.CharField(
|
|
705
|
+
max_length=10,
|
|
706
|
+
choices=ExportJobStatus.choices,
|
|
707
|
+
default=ExportJobStatus.PENDING,
|
|
708
|
+
db_index=True,
|
|
709
|
+
)
|
|
710
|
+
celery_task_id = models.CharField(max_length=255, blank=True, default="", db_index=True)
|
|
711
|
+
# Storage key (relative to the configured default storage), populated on success.
|
|
712
|
+
file_path = models.CharField(max_length=512, blank=True, default="")
|
|
713
|
+
file_size = models.BigIntegerField(null=True, blank=True)
|
|
714
|
+
error_detail = models.TextField(blank=True, default="")
|
|
715
|
+
# Download-link validity; past this the purge deletes the row + file (410 Gone).
|
|
716
|
+
expires_at = models.DateTimeField(null=True, blank=True)
|
|
717
|
+
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
718
|
+
started_at = models.DateTimeField(null=True, blank=True)
|
|
719
|
+
completed_at = models.DateTimeField(null=True, blank=True)
|
|
720
|
+
|
|
721
|
+
class Meta:
|
|
722
|
+
db_table = "workspace_export_job"
|
|
723
|
+
ordering = ["-created_at"]
|
|
724
|
+
indexes = [
|
|
725
|
+
# Drives the drain scan: pending rows awaiting (re-)dispatch.
|
|
726
|
+
models.Index(fields=["status", "created_at"], name="wsexport_status_created_idx"),
|
|
727
|
+
# Drives the nightly purge scan of expired download links.
|
|
728
|
+
models.Index(fields=["expires_at"], name="wsexport_expires_idx"),
|
|
729
|
+
]
|
|
730
|
+
|
|
731
|
+
def __str__(self) -> str:
|
|
732
|
+
return f"WorkspaceExportJob({self.id}, {self.status})"
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
class AuditEventType(models.TextChoices):
|
|
736
|
+
"""Operational audit event verbs (ADR-0157, #859).
|
|
737
|
+
|
|
738
|
+
The set is **additive-only**: new verbs may be appended over time, but an
|
|
739
|
+
existing value is never renamed or removed — enterprise audit receivers and
|
|
740
|
+
already-stored rows must stay valid against it.
|
|
741
|
+
"""
|
|
742
|
+
|
|
743
|
+
MEMBER_ADDED = "member_added", "Member added"
|
|
744
|
+
MEMBER_REMOVED = "member_removed", "Member removed"
|
|
745
|
+
MEMBER_ROLE_CHANGED = "member_role_changed", "Member role changed"
|
|
746
|
+
# #2867. A status-only PATCH used to flip is_active and a membership status —
|
|
747
|
+
# annoying, fully reversible, low audit value. Since #2832 it also revokes the
|
|
748
|
+
# target's refresh tokens and Personal Access Tokens, and that revocation is
|
|
749
|
+
# deliberately durable: reactivation does not restore them. The action's blast
|
|
750
|
+
# radius changed and its audit treatment did not, so one Admin could permanently
|
|
751
|
+
# destroy another member's credentials with the log holding no evidence — while
|
|
752
|
+
# the DELETE path, which does strictly less, recorded MEMBER_REMOVED.
|
|
753
|
+
MEMBER_STATUS_CHANGED = "member_status_changed", "Member status changed"
|
|
754
|
+
OWNERSHIP_TRANSFERRED = "ownership_transferred", "Ownership transferred"
|
|
755
|
+
PROJECT_CREATED = "project_created", "Project created"
|
|
756
|
+
PROJECT_DELETED = "project_deleted", "Project deleted"
|
|
757
|
+
PROJECT_RESTORED = "project_restored", "Project restored"
|
|
758
|
+
# #2909. Publishing takes one project's shape — task names included — and
|
|
759
|
+
# makes it readable by everyone in the workspace, which is a wider audience
|
|
760
|
+
# than the source project's own members. Republishing additionally changes
|
|
761
|
+
# what the house template resolves to for every future adopter. Both are
|
|
762
|
+
# Admin-gated; neither was legible afterwards.
|
|
763
|
+
TEMPLATE_PUBLISHED = "template_published", "Template published"
|
|
764
|
+
SETTINGS_CHANGED = "workspace_settings_changed", "Workspace settings changed"
|
|
765
|
+
EXPORT_TRIGGERED = "workspace_export_triggered", "Workspace export triggered"
|
|
766
|
+
# #2911. An invite is the account-provisioning path, and it was the one
|
|
767
|
+
# administrative action that erased its own record: the admin list filters to
|
|
768
|
+
# PENDING, so accepting or revoking made the row vanish from the only surface
|
|
769
|
+
# that showed it, and no verb here replaced it. INVITE_ACCEPTED additionally
|
|
770
|
+
# carries the inviter in metadata — the MEMBER_ADDED row written on the same
|
|
771
|
+
# transaction has the *invitee* as its actor (the accept endpoint is
|
|
772
|
+
# unauthenticated), so without this the log can say "X joined via invite" and
|
|
773
|
+
# never who sent it.
|
|
774
|
+
INVITE_SENT = "invite_sent", "Invite sent"
|
|
775
|
+
INVITE_ACCEPTED = "invite_accepted", "Invite accepted"
|
|
776
|
+
INVITE_REVOKED = "invite_revoked", "Invite revoked"
|
|
777
|
+
# #3174. Calendars are shared org-level resources gated on IsOrgAdmin, which
|
|
778
|
+
# passes anyone holding ADMIN on at least one *active* project (#3569 narrowed
|
|
779
|
+
# the derivation to exclude archived and soft-deleted projects; it did not
|
|
780
|
+
# change who may edit a calendar). Editing working_days,
|
|
781
|
+
# hours_per_day, or a single holiday exception fans a CPM recompute across every
|
|
782
|
+
# project bound to that calendar — including projects the actor is not a member
|
|
783
|
+
# of, whose owners saw their finish dates move with nothing naming who did it or
|
|
784
|
+
# why. The permission question is deliberately left open (see the issue); this
|
|
785
|
+
# verb is the attribution half, so the movement is at least traceable.
|
|
786
|
+
CALENDAR_CHANGED = "calendar_changed", "Calendar changed"
|
|
787
|
+
# #3552, ADR-1120. The auth surface recorded failures and no successes: an SSO
|
|
788
|
+
# provider could be created, repointed, widened, disabled or torn down, and its
|
|
789
|
+
# client secret rotated, with nothing naming who did it or what the value was
|
|
790
|
+
# before — while the serializer already treated an issuer repoint as dangerous
|
|
791
|
+
# enough to refuse. These five are the admin-actioned and once-per-identity half
|
|
792
|
+
# of that gap; login success is deliberately a ``trueppm.auth`` log line instead,
|
|
793
|
+
# because its rate is set by request traffic and this table has no OSS retention.
|
|
794
|
+
SSO_PROVIDER_CREATED = "sso_provider_created", "SSO provider created"
|
|
795
|
+
SSO_PROVIDER_UPDATED = "sso_provider_updated", "SSO provider updated"
|
|
796
|
+
SSO_PROVIDER_DELETED = "sso_provider_deleted", "SSO provider deleted"
|
|
797
|
+
SSO_SECRET_ROTATED = "sso_secret_rotated", "SSO client secret rotated"
|
|
798
|
+
# Branch 3 of ``sso.services.resolve_user`` — an IdP identity binding itself to an
|
|
799
|
+
# account that already exists. Branch 4 (auto-create) wrote MEMBER_ADDED and this
|
|
800
|
+
# one wrote nothing, so the path that grants a federated credential over an
|
|
801
|
+
# *existing* local account was the only one leaving no trace.
|
|
802
|
+
SSO_ACCOUNT_LINKED = "sso_account_linked", "SSO account linked"
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
class AuditEvent(models.Model):
|
|
806
|
+
"""Append-only* operational audit row for workspace administration (ADR-0157, #859).
|
|
807
|
+
|
|
808
|
+
*Mutable in OSS — the model makes no immutability/signing/retention guarantee;
|
|
809
|
+
those are Enterprise concerns layered on via the ``audit_event_created`` signal
|
|
810
|
+
(``workspace/signals.py``). OSS just records "who did what, when" at the
|
|
811
|
+
workspace-admin grain (member/role/project/settings/export changes).
|
|
812
|
+
|
|
813
|
+
Plain (non-synced) ``models.Model`` — like ``WorkspaceExportJob`` and
|
|
814
|
+
``SprintScopeChange``, an audit row is server-side operational metadata read
|
|
815
|
+
only through the Owner/Admin surface; it is **never** a mobile-offline sync
|
|
816
|
+
entity, so it carries no ``server_version``. There is no FK to ``Workspace``
|
|
817
|
+
because the OSS workspace is a singleton — a row always concerns the one
|
|
818
|
+
workspace (mirrors the ``WorkspaceExportJob`` rationale). The enterprise
|
|
819
|
+
receiver resolves the workspace itself when mirroring events into its
|
|
820
|
+
multi-tenant schema.
|
|
821
|
+
|
|
822
|
+
Actor and target are stored both as a best-effort FK/id **and** a denormalized
|
|
823
|
+
human-readable label, so the log stays readable after the user or target is
|
|
824
|
+
deleted (the ``SprintScopeChange.subtask_name`` idiom).
|
|
825
|
+
"""
|
|
826
|
+
|
|
827
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
828
|
+
actor = models.ForeignKey(
|
|
829
|
+
settings.AUTH_USER_MODEL,
|
|
830
|
+
on_delete=models.SET_NULL,
|
|
831
|
+
null=True,
|
|
832
|
+
blank=True,
|
|
833
|
+
related_name="audit_events",
|
|
834
|
+
)
|
|
835
|
+
# Denormalized actor display captured at event time (email/name) so the row
|
|
836
|
+
# remains human-readable even after the user is deleted (actor FK → NULL).
|
|
837
|
+
actor_label = models.CharField(max_length=255, blank=True, default="")
|
|
838
|
+
event_type = models.CharField(max_length=40, choices=AuditEventType.choices, db_index=True)
|
|
839
|
+
target_type = models.CharField(max_length=40, blank=True, default="")
|
|
840
|
+
# Best-effort pointer; may dangle after a hard delete — the denormalized
|
|
841
|
+
# ``target_label`` is the source of truth for display.
|
|
842
|
+
target_id = models.UUIDField(null=True, blank=True)
|
|
843
|
+
target_label = models.CharField(max_length=512, blank=True, default="")
|
|
844
|
+
metadata = models.JSONField(default=dict, blank=True)
|
|
845
|
+
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
|
846
|
+
|
|
847
|
+
class Meta:
|
|
848
|
+
db_table = "workspace_auditevent"
|
|
849
|
+
ordering = ["-created_at"]
|
|
850
|
+
indexes = [
|
|
851
|
+
# Covers the ``?event_type=`` filter combined with the default
|
|
852
|
+
# reverse-chron ordering. Name ≤ 30 chars (Postgres identifier limit).
|
|
853
|
+
models.Index(fields=["event_type", "created_at"], name="auditevent_type_created_idx"),
|
|
854
|
+
]
|
|
855
|
+
|
|
856
|
+
def __str__(self) -> str:
|
|
857
|
+
return f"AuditEvent({self.event_type}, {self.created_at:%Y-%m-%d})"
|