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,136 @@
|
|
|
1
|
+
"""Serializers for MS Project import provenance (#799)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from rest_framework import serializers
|
|
8
|
+
|
|
9
|
+
from trueppm_api.apps.msproject.models import ImportRequest
|
|
10
|
+
from trueppm_api.apps.taskruns.models import TaskRun
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ImportRequestProvenanceSerializer(serializers.ModelSerializer[ImportRequest]):
|
|
14
|
+
"""Read-only audit view of an `ImportRequest` for the project history list.
|
|
15
|
+
|
|
16
|
+
Marcus (PMO persona) wants the import itself to be part of the project's
|
|
17
|
+
record: who imported, from what file, when, and what was the outcome.
|
|
18
|
+
`ImportRequest` already holds the durable who/what/when/status fields;
|
|
19
|
+
this serializer joins in the linked `TaskRun.result_summary` to surface
|
|
20
|
+
the imported task count alongside the audit fields.
|
|
21
|
+
|
|
22
|
+
`ImportRequest` rows are purged after 7 days by the
|
|
23
|
+
``purge_old_import_requests`` cleanup task, so this surface is
|
|
24
|
+
intentionally an at-a-glance recent-activity view, not a long-lived
|
|
25
|
+
audit log — enterprise overlays consume the `history_record_created`
|
|
26
|
+
signal for compliance-grade retention (out of scope here).
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
initiated_by_username = serializers.SerializerMethodField()
|
|
30
|
+
task_count = serializers.SerializerMethodField()
|
|
31
|
+
warnings = serializers.SerializerMethodField()
|
|
32
|
+
|
|
33
|
+
class Meta:
|
|
34
|
+
model = ImportRequest
|
|
35
|
+
fields = (
|
|
36
|
+
"id",
|
|
37
|
+
"filename",
|
|
38
|
+
"status",
|
|
39
|
+
"creates_project",
|
|
40
|
+
"requested_at",
|
|
41
|
+
"initiated_by",
|
|
42
|
+
"initiated_by_username",
|
|
43
|
+
"task_count",
|
|
44
|
+
"warnings",
|
|
45
|
+
)
|
|
46
|
+
read_only_fields = fields
|
|
47
|
+
|
|
48
|
+
def get_initiated_by_username(self, obj: ImportRequest) -> str | None:
|
|
49
|
+
"""Display username; absent when the row's user was deleted."""
|
|
50
|
+
if obj.initiated_by_id is None:
|
|
51
|
+
return None
|
|
52
|
+
return obj.initiated_by.username if obj.initiated_by else None
|
|
53
|
+
|
|
54
|
+
def _task_run(self, obj: ImportRequest) -> TaskRun | None:
|
|
55
|
+
"""Resolve the linked TaskRun, memoized across the rows of one response.
|
|
56
|
+
|
|
57
|
+
Returns ``None`` when the import is still in flight (no TaskRun yet) or
|
|
58
|
+
when the run failed before writing its summary. The TaskRun link is by
|
|
59
|
+
`celery_task_id` — the import view sets it once Celery dispatch succeeds,
|
|
60
|
+
so PENDING / DEAD rows reliably resolve to ``None``.
|
|
61
|
+
|
|
62
|
+
Shared by every summary-derived field so a second one cannot double the
|
|
63
|
+
query count: the view pre-seeds a mutable `_taskrun_cache` dict in context
|
|
64
|
+
and we only ever read from `self.context` here (the type is
|
|
65
|
+
`Mapping[str, Any]` under stricter django-stubs builds in CI, so indexed
|
|
66
|
+
assignment is not safe). When invoked outside the list view (e.g.
|
|
67
|
+
single-instance detail use), no cache is present and each row issues its
|
|
68
|
+
own query.
|
|
69
|
+
"""
|
|
70
|
+
if not obj.celery_task_id:
|
|
71
|
+
return None
|
|
72
|
+
cache: dict[str, TaskRun | None] | None = self.context.get("_taskrun_cache")
|
|
73
|
+
if cache is not None and obj.celery_task_id in cache:
|
|
74
|
+
return cache[obj.celery_task_id]
|
|
75
|
+
run = TaskRun.objects.filter(celery_task_id=obj.celery_task_id).first()
|
|
76
|
+
if cache is not None:
|
|
77
|
+
cache[obj.celery_task_id] = run
|
|
78
|
+
return run
|
|
79
|
+
|
|
80
|
+
def get_task_count(self, obj: ImportRequest) -> int | None:
|
|
81
|
+
"""Pull the imported task count from the linked TaskRun summary."""
|
|
82
|
+
run = self._task_run(obj)
|
|
83
|
+
if run is None or not run.result_summary:
|
|
84
|
+
return None
|
|
85
|
+
value: Any = run.result_summary.get("task_count")
|
|
86
|
+
try:
|
|
87
|
+
return int(value) if value is not None else None
|
|
88
|
+
except (TypeError, ValueError):
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def get_warnings(self, obj: ImportRequest) -> list[str]:
|
|
92
|
+
"""Everything the import did not carry over, in the parser's order (#2891).
|
|
93
|
+
|
|
94
|
+
The importer has always written these into ``TaskRun.result_summary``, and
|
|
95
|
+
nothing has ever read them back out — so "the parser warns" was true and
|
|
96
|
+
useless at the same time. Losing an MS Project constraint, deadline or
|
|
97
|
+
baseline is a correctness event, not a log line, and the operator has to be
|
|
98
|
+
able to reach it. This is the API-side half; a wizard surface for it is
|
|
99
|
+
tracked separately (#2714).
|
|
100
|
+
|
|
101
|
+
Empty for a queued or dead import, the same contract as ``task_count``.
|
|
102
|
+
"""
|
|
103
|
+
run = self._task_run(obj)
|
|
104
|
+
if run is None or not run.result_summary:
|
|
105
|
+
return []
|
|
106
|
+
warnings: Any = run.result_summary.get("warnings")
|
|
107
|
+
if not isinstance(warnings, list):
|
|
108
|
+
return []
|
|
109
|
+
return [str(w) for w in warnings]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ImportProvenancePageSerializer(serializers.Serializer[dict[str, object]]):
|
|
113
|
+
"""The paginated envelope ``GET /projects/{id}/imports/`` actually returns (#2942).
|
|
114
|
+
|
|
115
|
+
Declared explicitly rather than left to ``OpenApiTypes.OBJECT``, which generates
|
|
116
|
+
``{"type": "object", "additionalProperties": {}}`` — machine-readable for nothing.
|
|
117
|
+
An SDK generated from that schema hands the caller an untyped bag: not ``results``,
|
|
118
|
+
not the pagination envelope, not ``task_count``, not ``warnings``. Every accuracy
|
|
119
|
+
problem #2942 lists exists *because* prose was the only description, so the fix is
|
|
120
|
+
to make the shape checkable rather than to write better prose.
|
|
121
|
+
|
|
122
|
+
This is a schema-only serializer — the view returns
|
|
123
|
+
``paginator.get_paginated_response(...)``, so nothing instantiates it at runtime.
|
|
124
|
+
It exists so ``responses={200: ...}`` has something faithful to point at, and it
|
|
125
|
+
must be kept in step with :class:`ImportProvenancePagination`'s envelope by hand;
|
|
126
|
+
there is no gate that compares a declared response shape to the real one.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
count = serializers.IntegerField(help_text="Total rows across all pages.")
|
|
130
|
+
next = serializers.URLField(
|
|
131
|
+
allow_null=True, help_text="URL of the next page, or null on the last page."
|
|
132
|
+
)
|
|
133
|
+
previous = serializers.URLField(
|
|
134
|
+
allow_null=True, help_text="URL of the previous page, or null on the first page."
|
|
135
|
+
)
|
|
136
|
+
results = ImportRequestProvenanceSerializer(many=True)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Service layer for MS Project import outbox operations.
|
|
2
|
+
|
|
3
|
+
Call sites:
|
|
4
|
+
- ``msproject.views`` — deferred via ``transaction.on_commit`` after the
|
|
5
|
+
ImportRequest row is committed
|
|
6
|
+
- ``msproject.tasks._do_import_drain`` — for each PENDING row the drain
|
|
7
|
+
picks up
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
from django.utils import timezone
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def enqueue_import(import_request_id: str) -> None:
|
|
20
|
+
"""Attempt to dispatch a PENDING ImportRequest row to Celery.
|
|
21
|
+
|
|
22
|
+
Reads the row by ID and tries to call ``import_msproject.delay()``.
|
|
23
|
+
On success the row is flipped to DISPATCHED. On broker failure the row
|
|
24
|
+
stays PENDING so ``drain_import_queue`` picks it up within 30 seconds.
|
|
25
|
+
|
|
26
|
+
Safe to call from ``transaction.on_commit()`` callbacks and from the
|
|
27
|
+
drain task body. If the row is no longer PENDING (already dispatched by
|
|
28
|
+
a concurrent call) the function is a no-op.
|
|
29
|
+
"""
|
|
30
|
+
from trueppm_api.apps.msproject.models import ImportRequest, ImportRequestStatus
|
|
31
|
+
from trueppm_api.apps.msproject.tasks import import_msproject
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
req = ImportRequest.objects.get(id=import_request_id, status=ImportRequestStatus.PENDING)
|
|
35
|
+
except ImportRequest.DoesNotExist:
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
result = import_msproject.delay(
|
|
40
|
+
project_id=str(req.project_id),
|
|
41
|
+
file_content_b64=req.file_content_b64,
|
|
42
|
+
filename=req.filename,
|
|
43
|
+
initiated_by_id=req.initiated_by_id,
|
|
44
|
+
import_request_id=str(req.id),
|
|
45
|
+
creates_project=req.creates_project,
|
|
46
|
+
)
|
|
47
|
+
except Exception:
|
|
48
|
+
logger.warning(
|
|
49
|
+
"enqueue_import: could not dispatch ImportRequest %s — drain will retry",
|
|
50
|
+
import_request_id,
|
|
51
|
+
)
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
ImportRequest.objects.filter(id=req.id, status=ImportRequestStatus.PENDING).update(
|
|
55
|
+
status=ImportRequestStatus.DISPATCHED,
|
|
56
|
+
celery_task_id=result.id,
|
|
57
|
+
dispatched_at=timezone.now(),
|
|
58
|
+
)
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""Celery tasks for MS Project import."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import logging
|
|
7
|
+
from collections.abc import Generator
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from datetime import timedelta
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from celery import shared_task
|
|
13
|
+
|
|
14
|
+
from trueppm_api.core.idempotent import idempotent_task
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from trueppm_api.apps.msproject.dataclasses import ProjectData
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
# An import task running longer than this is considered orphaned by the drain.
|
|
22
|
+
# Slightly longer than soft_time_limit (540 s = 9 min) to let the task time
|
|
23
|
+
# out and record its own failure before drain resets the row.
|
|
24
|
+
_IMPORT_ORPHAN_MINUTES = 15
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _NoOpTracker:
|
|
28
|
+
"""Fallback when TaskRunTracker is not available."""
|
|
29
|
+
|
|
30
|
+
task_run_id = None
|
|
31
|
+
|
|
32
|
+
def update(self, pct: int, msg: str = "") -> None:
|
|
33
|
+
logger.info("import.msproject progress: %d%% -- %s", pct, msg)
|
|
34
|
+
|
|
35
|
+
def set_result(self, result: dict[str, Any]) -> None:
|
|
36
|
+
logger.info("import.msproject result: %s", result)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@contextmanager
|
|
40
|
+
def _get_tracker(
|
|
41
|
+
celery_task: Any,
|
|
42
|
+
project_id: str,
|
|
43
|
+
initiated_by_id: int | None,
|
|
44
|
+
) -> Generator[Any, None, None]:
|
|
45
|
+
"""Try to use TaskRunTracker; fall back to no-op if not available."""
|
|
46
|
+
try:
|
|
47
|
+
from trueppm_api.apps.taskruns.tracker import TaskRunTracker
|
|
48
|
+
|
|
49
|
+
with TaskRunTracker(
|
|
50
|
+
celery_task,
|
|
51
|
+
project_id=project_id,
|
|
52
|
+
task_name="import.msproject",
|
|
53
|
+
initiated_by_id=initiated_by_id,
|
|
54
|
+
) as tracker:
|
|
55
|
+
yield tracker
|
|
56
|
+
except ImportError:
|
|
57
|
+
yield _NoOpTracker()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _parse_import_bytes(file_content: bytes, ext: str) -> ProjectData:
|
|
61
|
+
"""Parse ``.mpp``/``.xml`` bytes into the importer's project-data shape.
|
|
62
|
+
|
|
63
|
+
Defined above ``import_msproject`` rather than inside it so the ``@shared_task``
|
|
64
|
+
decorator below binds to the task and nothing else.
|
|
65
|
+
"""
|
|
66
|
+
if ext == "mpp":
|
|
67
|
+
from trueppm_api.apps.msproject.parser import parse_mpp
|
|
68
|
+
|
|
69
|
+
return parse_mpp(file_content)
|
|
70
|
+
if ext == "xml":
|
|
71
|
+
from trueppm_api.apps.msproject.parser import parse_xml
|
|
72
|
+
|
|
73
|
+
return parse_xml(file_content)
|
|
74
|
+
raise ValueError(f"Unsupported file format: .{ext}. Expected .mpp or .xml")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _reject_infeasible_graph(
|
|
78
|
+
project_data: ProjectData, filename: str, import_request_id: str | None
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Raise if the file's dependency graph is infeasible, before any write (ADR-0259).
|
|
81
|
+
|
|
82
|
+
A hand-maintained .mpp/.xml routinely carries a cyclic or self-referential
|
|
83
|
+
predecessor link, and the importer persists links with ``bulk_create`` — which
|
|
84
|
+
bypasses ``DependencySerializer``'s per-edge cycle check, the only other gate.
|
|
85
|
+
Without this the file writes an infeasible network that then crashes the CPM
|
|
86
|
+
engine on the next recalc: the exact bug ADR-0259 was written for.
|
|
87
|
+
|
|
88
|
+
Validated in the file's own uid space so a bad file is rejected while the project
|
|
89
|
+
is still empty rather than rolled back. The caller runs this OUTSIDE its
|
|
90
|
+
``atomic()`` block: raising inside would roll the outbox claim back to
|
|
91
|
+
DISPATCHED, so the orphan drain would re-dispatch a deterministically bad file
|
|
92
|
+
forever (ADR-0092) — the same reason a parse failure marks the row DEAD.
|
|
93
|
+
"""
|
|
94
|
+
from trueppm_api.apps.msproject.importer import (
|
|
95
|
+
collect_dependency_edges,
|
|
96
|
+
describe_bad_graph,
|
|
97
|
+
)
|
|
98
|
+
from trueppm_api.apps.scheduling.graph_guard import (
|
|
99
|
+
InfeasibleGraphError,
|
|
100
|
+
validate_task_graph,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
validate_task_graph(collect_dependency_edges(project_data))
|
|
105
|
+
except InfeasibleGraphError as exc:
|
|
106
|
+
# ImportRequest has no result_summary column to persist the detail on
|
|
107
|
+
# (unlike CsvImportRequest), so the TaskRunTracker's FAILED message is the
|
|
108
|
+
# only surface that outlives the worker — log the human-readable form for
|
|
109
|
+
# the operator as well (#2714).
|
|
110
|
+
logger.warning(
|
|
111
|
+
"import.msproject: rejected %s — %s",
|
|
112
|
+
filename,
|
|
113
|
+
describe_bad_graph(project_data, exc),
|
|
114
|
+
)
|
|
115
|
+
if import_request_id:
|
|
116
|
+
_mark_import_dead(import_request_id)
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@shared_task( # type: ignore[untyped-decorator]
|
|
121
|
+
bind=True,
|
|
122
|
+
name="msproject.import_file",
|
|
123
|
+
soft_time_limit=540,
|
|
124
|
+
time_limit=600,
|
|
125
|
+
acks_late=True,
|
|
126
|
+
reject_on_worker_lost=True,
|
|
127
|
+
)
|
|
128
|
+
def import_msproject(
|
|
129
|
+
self: Any,
|
|
130
|
+
project_id: str,
|
|
131
|
+
file_content_b64: str,
|
|
132
|
+
filename: str,
|
|
133
|
+
initiated_by_id: int | None = None,
|
|
134
|
+
import_request_id: str | None = None,
|
|
135
|
+
creates_project: bool = False,
|
|
136
|
+
) -> dict[str, Any]:
|
|
137
|
+
"""Import an MS Project file (.mpp or .xml) into a project.
|
|
138
|
+
|
|
139
|
+
The file content is passed as base64-encoded string to avoid binary
|
|
140
|
+
issues with the Celery/Redis message broker.
|
|
141
|
+
|
|
142
|
+
When dispatched via the ImportRequest outbox, import_request_id is set
|
|
143
|
+
so the row can be marked DONE on success (or DEAD on a terminal parse
|
|
144
|
+
failure).
|
|
145
|
+
|
|
146
|
+
When ``creates_project`` is True (create-from-import, ADR-0092) the project
|
|
147
|
+
shell was created synchronously named from the filename; the file header
|
|
148
|
+
overwrites its name/start_date once parsing succeeds, and the import wipes
|
|
149
|
+
any prior partial attempt so an orphan-drain re-dispatch is idempotent.
|
|
150
|
+
|
|
151
|
+
After import, triggers a CPM recalculation via the scheduling outbox.
|
|
152
|
+
"""
|
|
153
|
+
with _get_tracker(self, project_id, initiated_by_id) as tracker:
|
|
154
|
+
file_content = base64.b64decode(file_content_b64)
|
|
155
|
+
ext = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
|
|
156
|
+
|
|
157
|
+
tracker.update(5, f"Parsing {filename}...")
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
project_data = _parse_import_bytes(file_content, ext)
|
|
161
|
+
except Exception:
|
|
162
|
+
# A parse/format error is deterministic: retrying the same bytes will
|
|
163
|
+
# always fail. Mark the outbox row DEAD (terminal) so the orphan drain
|
|
164
|
+
# stops re-dispatching it forever (ADR-0092). The TaskRunTracker records
|
|
165
|
+
# the run FAILED with this message so the UI can surface it. Infra errors
|
|
166
|
+
# (broker, timeout, transient DB) happen later and leave the row
|
|
167
|
+
# DISPATCHED for the normal drain/retry path.
|
|
168
|
+
if import_request_id:
|
|
169
|
+
_mark_import_dead(import_request_id)
|
|
170
|
+
raise
|
|
171
|
+
|
|
172
|
+
# Runs OUTSIDE the atomic() block below — see the helper's docstring for
|
|
173
|
+
# why raising inside it would re-dispatch a bad file forever.
|
|
174
|
+
tracker.update(15, "Validating dependency graph...")
|
|
175
|
+
_reject_infeasible_graph(project_data, filename, import_request_id)
|
|
176
|
+
|
|
177
|
+
if creates_project:
|
|
178
|
+
_apply_header_to_project(project_id, project_data)
|
|
179
|
+
|
|
180
|
+
from django.db import transaction
|
|
181
|
+
|
|
182
|
+
from trueppm_api.apps.msproject.importer import (
|
|
183
|
+
broadcast_import_project_record_change,
|
|
184
|
+
import_project,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# Make the additive import-into-existing path idempotent under
|
|
188
|
+
# re-dispatch (#1673). The outbox row is the idempotency token: claim it
|
|
189
|
+
# (DISPATCHED -> DONE) and run the bulk_create in ONE transaction. A
|
|
190
|
+
# duplicate delivery — a drain re-dispatch after a worker-death window,
|
|
191
|
+
# or an acks_late / reject_on_worker_lost redelivery of an already
|
|
192
|
+
# completed message — finds no DISPATCHED row to claim, so it skips the
|
|
193
|
+
# import instead of bulk-creating the whole network again (which
|
|
194
|
+
# duplicated tasks and FS dependencies). The claim's row lock also
|
|
195
|
+
# serializes any concurrent delivery onto one winner. Because the claim
|
|
196
|
+
# and the import share the transaction, a mid-import failure rolls the
|
|
197
|
+
# claim back too, returning the row to DISPATCHED for a clean retry.
|
|
198
|
+
# (TaskRunTracker.update writes and broadcasts are already atomic-safe —
|
|
199
|
+
# see its docstring.)
|
|
200
|
+
with transaction.atomic():
|
|
201
|
+
if import_request_id and not _claim_import(import_request_id):
|
|
202
|
+
logger.info(
|
|
203
|
+
"import.msproject: request %s already completed — skipping duplicate delivery",
|
|
204
|
+
import_request_id,
|
|
205
|
+
)
|
|
206
|
+
return {"skipped": True, "tasks_created": 0}
|
|
207
|
+
summary = import_project(
|
|
208
|
+
project_id, project_data, tracker=tracker, wipe_existing=creates_project
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
tracker.set_result(summary)
|
|
212
|
+
|
|
213
|
+
# calendar_applied alone also needs a recalc (#1769): an into-existing
|
|
214
|
+
# import of a calendar-bearing file can change the project calendar even
|
|
215
|
+
# when the file contributed no tasks, and the existing tasks' dates must
|
|
216
|
+
# be recomputed on the new working mask.
|
|
217
|
+
if summary["tasks_created"] > 0 or summary.get("calendar_applied"):
|
|
218
|
+
# Use the outbox service rather than a direct .delay() call so that
|
|
219
|
+
# a broker outage at import-completion time does not silently drop
|
|
220
|
+
# the recalculation. The Celery task context has no ambient
|
|
221
|
+
# transaction, so enqueue_recalculate opens its own atomic() block.
|
|
222
|
+
from trueppm_api.apps.scheduling.services import enqueue_recalculate
|
|
223
|
+
|
|
224
|
+
enqueue_recalculate(project_id)
|
|
225
|
+
|
|
226
|
+
if summary["tasks_created"] > 0:
|
|
227
|
+
# #1359: a bulk import restructures the WBS, but clients had no signal
|
|
228
|
+
# until the async CPM pass eventually landed — a multi-second window of
|
|
229
|
+
# a stale, empty-looking task list. Emit tasks_restructured (the event
|
|
230
|
+
# the web client already invalidates the tasks cache on) so the imported
|
|
231
|
+
# tree appears immediately, ahead of the recalc's cpm_complete. Deferred
|
|
232
|
+
# via on_commit for the same reason as the project_updated broadcast
|
|
233
|
+
# below. It now runs just after the import's atomic() has committed
|
|
234
|
+
# (no ambient transaction remains → the callback fires immediately),
|
|
235
|
+
# so peers never see the restructure event for a rolled-back import.
|
|
236
|
+
from trueppm_api.apps.sync.broadcast import broadcast_board_event
|
|
237
|
+
|
|
238
|
+
transaction.on_commit(
|
|
239
|
+
lambda: broadcast_board_event(project_id, "tasks_restructured", {})
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
# #873/#867: an imported task that predated the project start pulled the
|
|
243
|
+
# start back (committed inside import_project). Broadcast project_updated
|
|
244
|
+
# so collaborators viewing an existing project re-fetch the new boundary
|
|
245
|
+
# — the recalc's cpm_complete event does not invalidate the project query.
|
|
246
|
+
# Deferred with on_commit (#1323): the start-pull now commits inside the
|
|
247
|
+
# import's atomic() above, and this runs just after it commits (no ambient
|
|
248
|
+
# transaction remains → the callback fires immediately), so peers re-fetch
|
|
249
|
+
# the moved boundary only once the import is durable, never on a rollback.
|
|
250
|
+
# calendar_applied rides the same broadcast (#1769): Project.calendar is
|
|
251
|
+
# project-record state (settings UI, sync clients), which cpm_complete
|
|
252
|
+
# does not invalidate.
|
|
253
|
+
broadcast_import_project_record_change(project_id, summary)
|
|
254
|
+
|
|
255
|
+
return summary
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _claim_import(import_request_id: str) -> bool:
|
|
259
|
+
"""Atomically claim a DISPATCHED ImportRequest, flipping it to DONE.
|
|
260
|
+
|
|
261
|
+
The single conditional ``UPDATE ... WHERE status=DISPATCHED`` is the
|
|
262
|
+
exactly-once idempotency token for the import (#1673): the caller runs the
|
|
263
|
+
claim inside the same transaction as the bulk_create, so the row transitions
|
|
264
|
+
to DONE atomically with the imported network. Returns ``True`` when this
|
|
265
|
+
delivery won the claim (the caller should import), ``False`` when the row was
|
|
266
|
+
already terminal — a duplicate delivery whose import must be skipped so the
|
|
267
|
+
additive path does not bulk-create the network twice.
|
|
268
|
+
|
|
269
|
+
Clears ``file_content_b64`` in the same update: the ~67 MB base64 payload
|
|
270
|
+
exists only so a PENDING/DISPATCHED row survives a broker outage and can be
|
|
271
|
+
re-dispatched by the drain (which re-reads it). Once the import reaches the
|
|
272
|
+
terminal DONE state it can never be retried, so the blob is dead weight —
|
|
273
|
+
null it now instead of carrying it until the nightly retention purge (#789).
|
|
274
|
+
"""
|
|
275
|
+
from trueppm_api.apps.msproject.models import ImportRequest, ImportRequestStatus
|
|
276
|
+
|
|
277
|
+
claimed = ImportRequest.objects.filter(
|
|
278
|
+
id=import_request_id, status=ImportRequestStatus.DISPATCHED
|
|
279
|
+
).update(status=ImportRequestStatus.DONE, file_content_b64="")
|
|
280
|
+
return bool(claimed)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _mark_import_dead(import_request_id: str) -> None:
|
|
284
|
+
"""Flip the ImportRequest row to DEAD after a terminal (parse) failure.
|
|
285
|
+
|
|
286
|
+
DEAD is excluded from the orphan-drain recovery, so a deterministically bad
|
|
287
|
+
file is not re-dispatched forever (ADR-0092). Because it is terminal and can
|
|
288
|
+
never be retried, the base64 payload is cleared here too for the same reason
|
|
289
|
+
as DONE — it is only needed for pre-terminal retry (#789).
|
|
290
|
+
"""
|
|
291
|
+
from trueppm_api.apps.msproject.models import ImportRequest, ImportRequestStatus
|
|
292
|
+
|
|
293
|
+
ImportRequest.objects.filter(id=import_request_id).exclude(
|
|
294
|
+
status=ImportRequestStatus.DONE
|
|
295
|
+
).update(status=ImportRequestStatus.DEAD, file_content_b64="")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _apply_header_to_project(project_id: str, data: ProjectData) -> None:
|
|
299
|
+
"""Overwrite a create-from-import shell's name/start_date from the file header.
|
|
300
|
+
|
|
301
|
+
The shell is created synchronously with a filename-derived name and today's
|
|
302
|
+
date (ADR-0092); the parsed ``<Name>``/``<StartDate>`` are authoritative once
|
|
303
|
+
the file reads cleanly. Uses ``.update()`` to bypass django-simple-history on
|
|
304
|
+
this one-shot rename (ADR-0011).
|
|
305
|
+
"""
|
|
306
|
+
from datetime import date
|
|
307
|
+
|
|
308
|
+
from trueppm_api.apps.projects.models import Project
|
|
309
|
+
|
|
310
|
+
fields: dict[str, Any] = {}
|
|
311
|
+
if data.name:
|
|
312
|
+
fields["name"] = data.name[:255]
|
|
313
|
+
if data.start_date:
|
|
314
|
+
try:
|
|
315
|
+
fields["start_date"] = date.fromisoformat(data.start_date)
|
|
316
|
+
except ValueError:
|
|
317
|
+
logger.warning("import.msproject: unparseable header start_date %r", data.start_date)
|
|
318
|
+
if fields:
|
|
319
|
+
Project.objects.filter(pk=project_id).update(**fields)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@idempotent_task(
|
|
323
|
+
lock_key_template="drain_import_queue",
|
|
324
|
+
lock_ttl=60,
|
|
325
|
+
on_contention="skip",
|
|
326
|
+
soft_time_limit=25,
|
|
327
|
+
time_limit=30,
|
|
328
|
+
acks_late=True,
|
|
329
|
+
reject_on_worker_lost=True,
|
|
330
|
+
name="msproject.drain_import_queue",
|
|
331
|
+
)
|
|
332
|
+
def drain_import_queue(self: object) -> None:
|
|
333
|
+
"""Dispatch any pending ImportRequest outbox rows.
|
|
334
|
+
|
|
335
|
+
Runs every 30 seconds via Celery Beat. Also recovers orphaned dispatched
|
|
336
|
+
rows older than _IMPORT_ORPHAN_MINUTES (import soft_time_limit is 9 min,
|
|
337
|
+
so 15 min ensures the prior task has either completed or timed out).
|
|
338
|
+
"""
|
|
339
|
+
_do_import_drain()
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@idempotent_task(
|
|
343
|
+
lock_key_template="purge_old_import_requests",
|
|
344
|
+
lock_ttl=120,
|
|
345
|
+
on_contention="skip",
|
|
346
|
+
soft_time_limit=55,
|
|
347
|
+
time_limit=90,
|
|
348
|
+
acks_late=True,
|
|
349
|
+
reject_on_worker_lost=True,
|
|
350
|
+
name="msproject.purge_old_import_requests",
|
|
351
|
+
)
|
|
352
|
+
def purge_old_import_requests(self: object) -> None:
|
|
353
|
+
"""Delete done/dead ImportRequest rows older than 7 days.
|
|
354
|
+
|
|
355
|
+
Runs nightly via Celery Beat. Keeps file_content_b64 blobs from
|
|
356
|
+
accumulating in the database indefinitely.
|
|
357
|
+
"""
|
|
358
|
+
_do_import_purge()
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _do_import_drain() -> None:
|
|
362
|
+
"""Business logic for drain_import_queue — extracted for testability."""
|
|
363
|
+
from django.utils import timezone
|
|
364
|
+
|
|
365
|
+
from trueppm_api.apps.msproject.models import ImportRequest, ImportRequestStatus
|
|
366
|
+
|
|
367
|
+
now = timezone.now()
|
|
368
|
+
orphan_cutoff = now - timedelta(minutes=_IMPORT_ORPHAN_MINUTES)
|
|
369
|
+
|
|
370
|
+
recovered = ImportRequest.objects.filter(
|
|
371
|
+
status=ImportRequestStatus.DISPATCHED,
|
|
372
|
+
dispatched_at__lt=orphan_cutoff,
|
|
373
|
+
).update(status=ImportRequestStatus.PENDING, celery_task_id="")
|
|
374
|
+
if recovered:
|
|
375
|
+
logger.warning("drain_import_queue: recovered %d orphaned row(s)", recovered)
|
|
376
|
+
|
|
377
|
+
pending = list(ImportRequest.objects.filter(status=ImportRequestStatus.PENDING))
|
|
378
|
+
dispatched = 0
|
|
379
|
+
for req in pending:
|
|
380
|
+
try:
|
|
381
|
+
result = import_msproject.delay(
|
|
382
|
+
project_id=str(req.project_id),
|
|
383
|
+
file_content_b64=req.file_content_b64,
|
|
384
|
+
filename=req.filename,
|
|
385
|
+
initiated_by_id=req.initiated_by_id,
|
|
386
|
+
import_request_id=str(req.id),
|
|
387
|
+
creates_project=req.creates_project,
|
|
388
|
+
)
|
|
389
|
+
except Exception:
|
|
390
|
+
logger.warning(
|
|
391
|
+
"drain_import_queue: broker unavailable — ImportRequest %s stays pending",
|
|
392
|
+
req.id,
|
|
393
|
+
)
|
|
394
|
+
continue
|
|
395
|
+
ImportRequest.objects.filter(id=req.id, status=ImportRequestStatus.PENDING).update(
|
|
396
|
+
status=ImportRequestStatus.DISPATCHED,
|
|
397
|
+
celery_task_id=result.id,
|
|
398
|
+
dispatched_at=now,
|
|
399
|
+
)
|
|
400
|
+
dispatched += 1
|
|
401
|
+
|
|
402
|
+
if dispatched or recovered:
|
|
403
|
+
logger.info("drain_import_queue: dispatched=%d recovered=%d", dispatched, recovered)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _do_import_purge(*, dry_run: bool = False, override_value: int | None = None) -> int:
|
|
407
|
+
"""Business logic for purge_old_import_requests — extracted for testability.
|
|
408
|
+
|
|
409
|
+
Retention comes from ``resolve_retention`` (operator override → the
|
|
410
|
+
TRUEPPM_IMPORT_RETENTION_DAYS default, ADR-0173); ``None`` disables the purge,
|
|
411
|
+
keeping ImportRequest blobs indefinitely. Returns rows deleted, or the eligible
|
|
412
|
+
count when ``dry_run``; ``override_value`` forces a hypothetical window.
|
|
413
|
+
"""
|
|
414
|
+
from django.utils import timezone
|
|
415
|
+
|
|
416
|
+
from trueppm_api.apps.msproject.models import ImportRequest, ImportRequestStatus
|
|
417
|
+
from trueppm_api.apps.observability.retention import resolve_retention
|
|
418
|
+
|
|
419
|
+
retention_days = (
|
|
420
|
+
override_value
|
|
421
|
+
if override_value is not None
|
|
422
|
+
else resolve_retention("TRUEPPM_IMPORT_RETENTION_DAYS")
|
|
423
|
+
)
|
|
424
|
+
if retention_days is None:
|
|
425
|
+
return 0
|
|
426
|
+
|
|
427
|
+
cutoff = timezone.now() - timedelta(days=retention_days)
|
|
428
|
+
qs = ImportRequest.objects.filter(
|
|
429
|
+
status__in=[ImportRequestStatus.DONE, ImportRequestStatus.DEAD],
|
|
430
|
+
requested_at__lt=cutoff,
|
|
431
|
+
)
|
|
432
|
+
if dry_run:
|
|
433
|
+
return qs.count()
|
|
434
|
+
deleted, _ = qs.delete()
|
|
435
|
+
logger.info("purge_old_import_requests: deleted %d row(s)", deleted)
|
|
436
|
+
return deleted
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""URL routing for MS Project import/export."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from django.urls import path
|
|
6
|
+
|
|
7
|
+
from trueppm_api.apps.msproject.views import (
|
|
8
|
+
CreateProjectFromMsProjectView,
|
|
9
|
+
ImportRequestProvenanceListView,
|
|
10
|
+
MsProjectExportView,
|
|
11
|
+
MsProjectImportView,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
urlpatterns = [
|
|
15
|
+
# Create-from-import (ADR-0092). Declared before the <project_pk> route and
|
|
16
|
+
# mounted ahead of the projects router so the literal "import" segment is not
|
|
17
|
+
# captured as a project primary key.
|
|
18
|
+
path(
|
|
19
|
+
"projects/import/msproject/",
|
|
20
|
+
CreateProjectFromMsProjectView.as_view(),
|
|
21
|
+
name="project-create-from-msproject",
|
|
22
|
+
),
|
|
23
|
+
path(
|
|
24
|
+
"projects/<project_pk>/import/msproject/",
|
|
25
|
+
MsProjectImportView.as_view(),
|
|
26
|
+
name="project-import-msproject",
|
|
27
|
+
),
|
|
28
|
+
# Import provenance list — read-only audit / "Imported from ..." source (#799).
|
|
29
|
+
path(
|
|
30
|
+
"projects/<project_pk>/imports/",
|
|
31
|
+
ImportRequestProvenanceListView.as_view(),
|
|
32
|
+
name="project-imports-list",
|
|
33
|
+
),
|
|
34
|
+
path(
|
|
35
|
+
"projects/<project_pk>/export/msproject.xml",
|
|
36
|
+
MsProjectExportView.as_view(),
|
|
37
|
+
name="project-export-msproject",
|
|
38
|
+
),
|
|
39
|
+
]
|