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,82 @@
|
|
|
1
|
+
"""Enterprise extension signal for object change history.
|
|
2
|
+
|
|
3
|
+
OSS fires ``history_record_created`` after every django-simple-history
|
|
4
|
+
``HistoricalRecord`` save. The enterprise package connects a receiver here to
|
|
5
|
+
implement unlimited retention, cold-storage export, and immutable audit
|
|
6
|
+
stamping — without any modification to OSS code.
|
|
7
|
+
|
|
8
|
+
Signal contract
|
|
9
|
+
---------------
|
|
10
|
+
- ``sender`` : the ``Historical<Model>`` class whose row was written —
|
|
11
|
+
``HistoricalTask``, ``HistoricalProject`` or ``HistoricalDependency``. A
|
|
12
|
+
receiver that wants one model's history connects with
|
|
13
|
+
``sender=Task.history.model``; one that wants all three connects with no
|
|
14
|
+
sender filter.
|
|
15
|
+
- ``instance`` : the original model instance that was mutated
|
|
16
|
+
- ``history_instance`` : the ``HistoricalRecord`` that was just saved
|
|
17
|
+
- ``history_type`` : ``"+"`` (create), ``"~"`` (update), ``"-"`` (delete)
|
|
18
|
+
|
|
19
|
+
Dispatched **inline**, from the ``post_save`` of the historical row, so it runs
|
|
20
|
+
inside the caller's transaction. ``instance`` is therefore a read-only snapshot
|
|
21
|
+
of a row that may still roll back: receivers must not save it and must defer any
|
|
22
|
+
I/O with ``transaction.on_commit()`` (see ``core/extension_signals.py`` for the
|
|
23
|
+
sender and kwarg conventions this follows).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import django.dispatch
|
|
29
|
+
from django.db.models.signals import post_save
|
|
30
|
+
|
|
31
|
+
from trueppm_api.core.extension_signals import dispatch_extension_signal
|
|
32
|
+
|
|
33
|
+
history_record_created = django.dispatch.Signal()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _dispatch_history_record_created(
|
|
37
|
+
sender: type,
|
|
38
|
+
instance: object,
|
|
39
|
+
created: bool,
|
|
40
|
+
**kwargs: object,
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Fire ``history_record_created`` after any HistoricalRecord is saved."""
|
|
43
|
+
# django-simple-history always creates new historical rows (never updates),
|
|
44
|
+
# so we only need the ``created=True`` path.
|
|
45
|
+
if not created:
|
|
46
|
+
return
|
|
47
|
+
# The HistoricalRecord instance carries ``instance`` (the tracked object)
|
|
48
|
+
# and ``history_type`` as attributes.
|
|
49
|
+
tracked = getattr(instance, "instance", None)
|
|
50
|
+
history_type = getattr(instance, "history_type", None)
|
|
51
|
+
# Robust dispatch is load-bearing here, not defensive style: this runs inside
|
|
52
|
+
# the post_save of every Historical* row for Project, Task and Dependency, so
|
|
53
|
+
# a raising enterprise receiver would fail every task save in the product
|
|
54
|
+
# (#2606).
|
|
55
|
+
# sender is the ``Historical<Model>`` class — the row this signal is about,
|
|
56
|
+
# reachable Enterprise-side as ``Task.history.model``. Written as
|
|
57
|
+
# ``type(instance)`` rather than the ``sender`` parameter so the class is
|
|
58
|
+
# visible at the call site: ``sender`` here is post_save's argument and
|
|
59
|
+
# reads as a pass-through of something the dispatcher chose (#3777).
|
|
60
|
+
dispatch_extension_signal(
|
|
61
|
+
history_record_created,
|
|
62
|
+
sender=type(instance),
|
|
63
|
+
instance=tracked,
|
|
64
|
+
history_instance=instance,
|
|
65
|
+
history_type=history_type,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _connect_to_historical_models() -> None:
|
|
70
|
+
"""Wire the receiver to every Historical* model generated by simple_history.
|
|
71
|
+
|
|
72
|
+
Called from HistoryConfig.ready() after all apps are loaded so the
|
|
73
|
+
Historical* models exist in the app registry.
|
|
74
|
+
"""
|
|
75
|
+
from trueppm_api.apps.projects.models import Dependency, Project, Task
|
|
76
|
+
|
|
77
|
+
for model in (Project, Task, Dependency):
|
|
78
|
+
historical_model = model.history.model
|
|
79
|
+
post_save.connect(_dispatch_history_record_created, sender=historical_model)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_connect_to_historical_models()
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Celery tasks for object change history maintenance."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from django.db import OperationalError
|
|
10
|
+
from django.utils import timezone
|
|
11
|
+
|
|
12
|
+
from trueppm_api.core.idempotent import idempotent_task
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _history_purge_counts(
|
|
18
|
+
*, dry_run: bool = False, override_value: int | None = None
|
|
19
|
+
) -> dict[str, int]:
|
|
20
|
+
"""Return ``{model_name: rows}`` purged (or eligible, when ``dry_run``).
|
|
21
|
+
|
|
22
|
+
The window comes from ``resolve_retention`` (operator override → the
|
|
23
|
+
HISTORY_RETENTION_DAYS default, ADR-0173); ``None`` disables the purge and
|
|
24
|
+
yields an empty dict. ``override_value`` forces a hypothetical window.
|
|
25
|
+
"""
|
|
26
|
+
from trueppm_api.apps.observability.retention import resolve_retention
|
|
27
|
+
from trueppm_api.apps.projects.models import Dependency, Project, Task, TaskActivityEvent
|
|
28
|
+
|
|
29
|
+
retention_days = (
|
|
30
|
+
override_value
|
|
31
|
+
if override_value is not None
|
|
32
|
+
else resolve_retention("HISTORY_RETENTION_DAYS")
|
|
33
|
+
)
|
|
34
|
+
if retention_days is None:
|
|
35
|
+
return {}
|
|
36
|
+
|
|
37
|
+
cutoff = timezone.now() - timedelta(days=retention_days)
|
|
38
|
+
totals: dict[str, int] = {}
|
|
39
|
+
for model in (Project, Task, Dependency):
|
|
40
|
+
qs = model.history.filter(history_date__lt=cutoff)
|
|
41
|
+
totals[model.__name__] = qs.count() if dry_run else qs.delete()[0]
|
|
42
|
+
# TaskActivityEvent (ADR-0207) is an append-only per-task audit source backing
|
|
43
|
+
# the same activity feed as the diff history, so it ages out on the SAME
|
|
44
|
+
# HISTORY_RETENTION_DAYS window — keeping the feed's two row sources consistent
|
|
45
|
+
# and bounding the cpm_recalculated growth that a busy schedule generates. It is
|
|
46
|
+
# a plain model (no simple-history), so it is keyed on created_at.
|
|
47
|
+
activity_qs = TaskActivityEvent.objects.filter(created_at__lt=cutoff)
|
|
48
|
+
totals["TaskActivityEvent"] = activity_qs.count() if dry_run else activity_qs.delete()[0]
|
|
49
|
+
return totals
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _do_history_purge(*, dry_run: bool = False, override_value: int | None = None) -> int:
|
|
53
|
+
"""Total historical rows purged (or eligible, when ``dry_run``) across all
|
|
54
|
+
history tables. The coordinator-facing entry point (ADR-0173 §C)."""
|
|
55
|
+
return sum(_history_purge_counts(dry_run=dry_run, override_value=override_value).values())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@idempotent_task(
|
|
59
|
+
lock_key_template="history_purge",
|
|
60
|
+
lock_ttl=600,
|
|
61
|
+
on_contention="skip",
|
|
62
|
+
name="history.purge_old_records",
|
|
63
|
+
autoretry_for=(ConnectionError, OperationalError),
|
|
64
|
+
retry_backoff=60,
|
|
65
|
+
retry_backoff_max=600,
|
|
66
|
+
retry_jitter=True,
|
|
67
|
+
max_retries=3,
|
|
68
|
+
soft_time_limit=300,
|
|
69
|
+
time_limit=360,
|
|
70
|
+
acks_late=True,
|
|
71
|
+
)
|
|
72
|
+
def purge_old_history_records(self: object) -> dict[str, Any]:
|
|
73
|
+
"""Delete historical records older than the resolved HISTORY retention window.
|
|
74
|
+
|
|
75
|
+
Still dispatchable directly, but no longer on its own Beat schedule — the
|
|
76
|
+
consolidated retention coordinator owns scheduled purging (ADR-0173 §C).
|
|
77
|
+
Returns a summary dict for logging and monitoring.
|
|
78
|
+
|
|
79
|
+
When the window resolves to None the task exits immediately — this disables
|
|
80
|
+
automatic purging for deployments that retain records indefinitely (or archive
|
|
81
|
+
them to cold storage via the history_record_created signal before purging).
|
|
82
|
+
"""
|
|
83
|
+
from trueppm_api.apps.observability.retention import resolve_retention
|
|
84
|
+
|
|
85
|
+
retention_days = resolve_retention("HISTORY_RETENTION_DAYS")
|
|
86
|
+
if retention_days is None:
|
|
87
|
+
logger.info("purge_old_history_records: retention disabled, skipping purge")
|
|
88
|
+
return {"status": "skipped", "reason": "unlimited retention"}
|
|
89
|
+
|
|
90
|
+
cutoff = timezone.now() - timedelta(days=retention_days)
|
|
91
|
+
totals = _history_purge_counts()
|
|
92
|
+
for label, deleted in totals.items():
|
|
93
|
+
if deleted:
|
|
94
|
+
logger.info(
|
|
95
|
+
"purge_old_history_records: deleted %d Historical%s rows older than %s",
|
|
96
|
+
deleted,
|
|
97
|
+
label,
|
|
98
|
+
cutoff.date(),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
total = sum(totals.values())
|
|
102
|
+
logger.info(
|
|
103
|
+
"purge_old_history_records: total %d rows deleted (cutoff=%s)", total, cutoff.date()
|
|
104
|
+
)
|
|
105
|
+
return {"status": "ok", "deleted": totals, "cutoff": cutoff.isoformat()}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""URL routing for the history app."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from django.urls import path
|
|
6
|
+
|
|
7
|
+
from trueppm_api.apps.history.views import (
|
|
8
|
+
ProjectChangelogView,
|
|
9
|
+
ProjectHistoryListView,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
urlpatterns = [
|
|
13
|
+
path(
|
|
14
|
+
"projects/<project_pk>/changelog/",
|
|
15
|
+
ProjectChangelogView.as_view(),
|
|
16
|
+
name="project-changelog",
|
|
17
|
+
),
|
|
18
|
+
path(
|
|
19
|
+
"projects/<project_pk>/history/",
|
|
20
|
+
ProjectHistoryListView.as_view(),
|
|
21
|
+
name="project-history-list",
|
|
22
|
+
),
|
|
23
|
+
# NOTE(#3372): ``projects/<project_pk>/history/summary/``
|
|
24
|
+
# (ProjectHistorySummaryView, ``project-history-summary``) was removed
|
|
25
|
+
# outright rather than deprecated — it shipped in 0.1 with a docstring
|
|
26
|
+
# claiming the UI called it on refresh, but had zero client consumers
|
|
27
|
+
# (web uses ``/changelog/`` and task history instead). See ADR-0011's
|
|
28
|
+
# dated Superseded note and stability.md step 3 for the recorded
|
|
29
|
+
# decision and rationale. The path now falls through to Django's
|
|
30
|
+
# standard 404, which is the deliberate outcome for a stale caller.
|
|
31
|
+
#
|
|
32
|
+
# NOTE: ``projects/<project_pk>/tasks/<task_pk>/history/`` is intentionally
|
|
33
|
+
# NOT registered here. The projects app already serves that path via
|
|
34
|
+
# ``project-task-history`` (TaskHistoryView), and because ``projects.urls``
|
|
35
|
+
# is included before ``history.urls`` in the root URLConf, a registration
|
|
36
|
+
# here would be permanently shadowed (dead route). The duplicate was removed
|
|
37
|
+
# per issue #781 so the OpenAPI schema shows a single task-history operation.
|
|
38
|
+
]
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""Views for the object change history API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import functools
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from django.contrib.auth import get_user_model
|
|
10
|
+
from django.shortcuts import get_object_or_404
|
|
11
|
+
from django.utils import timezone
|
|
12
|
+
from django.utils.dateparse import parse_datetime
|
|
13
|
+
from drf_spectacular.utils import (
|
|
14
|
+
OpenApiParameter,
|
|
15
|
+
OpenApiResponse,
|
|
16
|
+
extend_schema,
|
|
17
|
+
inline_serializer,
|
|
18
|
+
)
|
|
19
|
+
from rest_framework import serializers as drf_serializers
|
|
20
|
+
from rest_framework.exceptions import ValidationError as DRFValidationError
|
|
21
|
+
from rest_framework.pagination import PageNumberPagination
|
|
22
|
+
from rest_framework.permissions import IsAuthenticated
|
|
23
|
+
from rest_framework.request import Request
|
|
24
|
+
from rest_framework.response import Response
|
|
25
|
+
from rest_framework.views import APIView
|
|
26
|
+
|
|
27
|
+
from trueppm_api.apps.access.models import ProjectMembership, Role
|
|
28
|
+
from trueppm_api.apps.access.permissions import IsProjectMember
|
|
29
|
+
from trueppm_api.apps.history import changelog
|
|
30
|
+
from trueppm_api.apps.history.diff_policy import (
|
|
31
|
+
HISTORY_DIFF_NOISE,
|
|
32
|
+
is_compared,
|
|
33
|
+
visible_changes,
|
|
34
|
+
)
|
|
35
|
+
from trueppm_api.apps.history.serializers import (
|
|
36
|
+
ChangelogResponseSerializer,
|
|
37
|
+
HistoryRecordSerializer,
|
|
38
|
+
)
|
|
39
|
+
from trueppm_api.apps.projects.models import Project, Task
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
User = get_user_model()
|
|
44
|
+
|
|
45
|
+
# Which fields a diff compares, hides, and promotes is decided ONCE, in
|
|
46
|
+
# ``history.diff_policy`` — shared with the task drawer's pipeline in
|
|
47
|
+
# ``apps/projects/views.py`` (#3435). Do not add a local exclusion set here.
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class HistoryPagination(PageNumberPagination):
|
|
51
|
+
page_size = 50
|
|
52
|
+
page_size_query_param = "page_size"
|
|
53
|
+
max_page_size = 200
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _build_prev_map(records: list[Any]) -> dict[int, Any | None]:
|
|
57
|
+
"""Return history_id → previous HistoricalRecord without extra DB queries.
|
|
58
|
+
|
|
59
|
+
Groups records by their original-object PK, sorts each group by
|
|
60
|
+
history_date ascending, and pairs adjacent records in Python. Avoids
|
|
61
|
+
the per-record DB round-trip that record.prev_record triggers.
|
|
62
|
+
"""
|
|
63
|
+
by_obj: dict[Any, list[Any]] = {}
|
|
64
|
+
for r in records:
|
|
65
|
+
by_obj.setdefault(r.id, []).append(r)
|
|
66
|
+
prev_map: dict[int, Any | None] = {}
|
|
67
|
+
for group in by_obj.values():
|
|
68
|
+
group.sort(key=lambda r: r.history_date)
|
|
69
|
+
for i, r in enumerate(group):
|
|
70
|
+
prev_map[r.history_id] = group[i - 1] if i > 0 else None
|
|
71
|
+
return prev_map
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@functools.cache
|
|
75
|
+
def _compared_fields(model: Any, *, object_scoped: bool) -> tuple[Any, ...]:
|
|
76
|
+
"""The historical model's fields this pipeline compares (``diff_policy``).
|
|
77
|
+
|
|
78
|
+
Keyed by model class, not record: every record in a batch shares one model (a
|
|
79
|
+
per-object list is one model; the changelog diffs one model per source; the
|
|
80
|
+
summary aggregates three single-model batches of up to ``_MAX_HISTORY_ROWS``),
|
|
81
|
+
so re-filtering ~60 fields through the policy per row was pure repeat work.
|
|
82
|
+
The set of historical models is small and static for the process lifetime.
|
|
83
|
+
"""
|
|
84
|
+
return tuple(f for f in model._meta.fields if is_compared(f.name, object_scoped=object_scoped))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _compute_diffs(
|
|
88
|
+
records: list[Any],
|
|
89
|
+
all_records: list[Any] | None = None,
|
|
90
|
+
*,
|
|
91
|
+
object_scoped: bool = True,
|
|
92
|
+
) -> dict[int, list[dict[str, Any]]]:
|
|
93
|
+
"""Return history_id → field-diff list for a batch of HistoricalRecords.
|
|
94
|
+
|
|
95
|
+
Compares each record against its predecessor. Records with no visible change
|
|
96
|
+
map to an empty list; the view omits those from the response so CPM-only
|
|
97
|
+
mutations (or future excluded-field updates) don't produce noise. A change in
|
|
98
|
+
``HISTORY_DIFF_PROMOTED_WHEN_ALONE`` is kept only when it is the record's sole
|
|
99
|
+
change (#3306) — the same rule the task drawer applies.
|
|
100
|
+
|
|
101
|
+
Each row is keyed by the model **field name** (``assignee``), not the column
|
|
102
|
+
(``assignee_id``): the client's one label map is keyed on names, and the drawer
|
|
103
|
+
pipeline already emits them, so emitting columns here is what left the project
|
|
104
|
+
Activity page rendering raw identifiers (#3435). Values are read through
|
|
105
|
+
``attname`` so a relation yields its id rather than a lazy fetch per row.
|
|
106
|
+
|
|
107
|
+
``object_scoped`` says whether the caller renders ONE object's own history
|
|
108
|
+
(``True``: the per-object list views, where the object's tombstone flag is not a
|
|
109
|
+
field change worth a row) or a cross-object stream (``False``: the project
|
|
110
|
+
changelog, where that flag is the only record a soft delete left).
|
|
111
|
+
|
|
112
|
+
Pass all_records (the full unpaginated list for the same object) so that
|
|
113
|
+
the first record on a page can find its predecessor even when it sits on
|
|
114
|
+
a different page.
|
|
115
|
+
"""
|
|
116
|
+
prev_map = _build_prev_map(all_records if all_records is not None else records)
|
|
117
|
+
result: dict[int, list[dict[str, Any]]] = {}
|
|
118
|
+
for record in records:
|
|
119
|
+
prev = prev_map.get(record.history_id)
|
|
120
|
+
model: Any = type(record)
|
|
121
|
+
fields = _compared_fields(model, object_scoped=object_scoped)
|
|
122
|
+
if prev is None:
|
|
123
|
+
# Creation — list non-null fields as old=None → new=value. Promotion is
|
|
124
|
+
# a rule about updates, so promotable noise is dropped here outright.
|
|
125
|
+
changes: list[dict[str, Any]] = [
|
|
126
|
+
{"field": f.name, "old": None, "new": getattr(record, f.attname)}
|
|
127
|
+
for f in fields
|
|
128
|
+
if f.name not in HISTORY_DIFF_NOISE and getattr(record, f.attname) is not None
|
|
129
|
+
]
|
|
130
|
+
else:
|
|
131
|
+
changes = visible_changes(
|
|
132
|
+
(
|
|
133
|
+
{
|
|
134
|
+
"field": f.name,
|
|
135
|
+
"old": getattr(prev, f.attname),
|
|
136
|
+
"new": getattr(record, f.attname),
|
|
137
|
+
}
|
|
138
|
+
for f in fields
|
|
139
|
+
if getattr(record, f.attname) != getattr(prev, f.attname)
|
|
140
|
+
),
|
|
141
|
+
lambda change: str(change["field"]),
|
|
142
|
+
)
|
|
143
|
+
result[record.history_id] = changes
|
|
144
|
+
return result
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _caller_can_see_user(request: Request, project: Project) -> bool:
|
|
148
|
+
"""True if the caller holds Owner or Admin role (>= Role.ADMIN)."""
|
|
149
|
+
try:
|
|
150
|
+
m = ProjectMembership.objects.get(
|
|
151
|
+
user=request.user, # type: ignore[misc]
|
|
152
|
+
project=project,
|
|
153
|
+
is_deleted=False,
|
|
154
|
+
)
|
|
155
|
+
return m.role >= Role.ADMIN
|
|
156
|
+
except ProjectMembership.DoesNotExist:
|
|
157
|
+
return False
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Cap the number of history rows materialized in memory for the per-task and
|
|
161
|
+
# per-project history list views. Without this cap a task with thousands of
|
|
162
|
+
# edits (a busy automated integration, or a re-import loop) would load the full
|
|
163
|
+
# history table into Python on every page request.
|
|
164
|
+
#
|
|
165
|
+
# NOTE(#1889): this cap intentionally differs from the projects app's
|
|
166
|
+
# ``apps.projects.views._MAX_HISTORY_ROWS`` (2000), which backs the wired
|
|
167
|
+
# ``/projects/<pk>/tasks/<pk>/history/`` route serving the task drawer.
|
|
168
|
+
# ``TaskHistoryListView`` below is deliberately unwired (see this app's
|
|
169
|
+
# ``urls.py``); the 5000 bound here covers the project-wide list view. Do not
|
|
170
|
+
# "sync" the two numbers; cross-reference comments live at both constants.
|
|
171
|
+
#
|
|
172
|
+
# NOTE(#3372): this constant previously was also shared with
|
|
173
|
+
# ``ProjectHistorySummaryView``, an orphaned endpoint (zero client consumers
|
|
174
|
+
# since it shipped in 0.1) removed outright rather than deprecated — see
|
|
175
|
+
# ADR-0011's dated Superseded note and stability.md step 3. That class carried
|
|
176
|
+
# its own identical-value class attribute of the same name, which is gone now
|
|
177
|
+
# too; nothing here refers to it any longer.
|
|
178
|
+
_MAX_HISTORY_ROWS = 5000
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _history_page_response(
|
|
182
|
+
request: Request,
|
|
183
|
+
view: APIView,
|
|
184
|
+
project: Project,
|
|
185
|
+
history_queryset: Any,
|
|
186
|
+
) -> Response:
|
|
187
|
+
"""Shared GET body for the per-task and per-project history list views.
|
|
188
|
+
|
|
189
|
+
Fetches cap+1 rows to detect truncation, computes diffs (passing the
|
|
190
|
+
untrimmed cap+1 batch as the diff seed for the oldest kept record, #1889),
|
|
191
|
+
hides ``history_user`` for callers below Owner/Admin, and returns the
|
|
192
|
+
paginated, diff-filtered response with ``count_truncated`` grafted on.
|
|
193
|
+
"""
|
|
194
|
+
raw: list[Any] = list(
|
|
195
|
+
history_queryset.order_by("-history_date").select_related("history_user")[
|
|
196
|
+
: _MAX_HISTORY_ROWS + 1
|
|
197
|
+
]
|
|
198
|
+
)
|
|
199
|
+
count_truncated = len(raw) > _MAX_HISTORY_ROWS
|
|
200
|
+
records = raw[:_MAX_HISTORY_ROWS]
|
|
201
|
+
|
|
202
|
+
paginator = HistoryPagination()
|
|
203
|
+
page: list[Any] = paginator.paginate_queryset(records, request, view=view) or records
|
|
204
|
+
|
|
205
|
+
diffs = _compute_diffs(page, all_records=raw)
|
|
206
|
+
hide_user = not _caller_can_see_user(request, project)
|
|
207
|
+
visible = [r for r in page if diffs.get(r.history_id)]
|
|
208
|
+
serializer = HistoryRecordSerializer(
|
|
209
|
+
visible,
|
|
210
|
+
many=True,
|
|
211
|
+
context={"diffs": diffs, "hide_user": hide_user},
|
|
212
|
+
)
|
|
213
|
+
response = paginator.get_paginated_response(serializer.data)
|
|
214
|
+
response.data["count_truncated"] = count_truncated
|
|
215
|
+
return response
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class TaskHistoryListView(APIView):
|
|
219
|
+
"""Paginated change history for a single task.
|
|
220
|
+
|
|
221
|
+
GET /api/v1/projects/{project_pk}/tasks/{task_pk}/history/
|
|
222
|
+
|
|
223
|
+
Permissions: any project member (Viewer+) may read. history_user details
|
|
224
|
+
are visible only to Owner/Admin (role >= Role.ADMIN); lower-privilege callers
|
|
225
|
+
receive null for that field.
|
|
226
|
+
|
|
227
|
+
At most ``_MAX_HISTORY_ROWS`` records are materialized; ``count_truncated``
|
|
228
|
+
in the response is ``true`` when the cap was hit so the client can surface
|
|
229
|
+
"showing recent activity" instead of implying a complete record.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# IsProjectNotArchived is deliberately omitted: history is a read-only audit
|
|
233
|
+
# surface that must stay accessible after a project is archived. Do not add it
|
|
234
|
+
# "for consistency" — archived projects still need their audit trail readable.
|
|
235
|
+
permission_classes = [IsAuthenticated, IsProjectMember]
|
|
236
|
+
|
|
237
|
+
def get(self, request: Request, project_pk: str, task_pk: str) -> Response:
|
|
238
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
239
|
+
self.check_object_permissions(request, project)
|
|
240
|
+
task = get_object_or_404(Task, pk=task_pk, project_id=project_pk, is_deleted=False)
|
|
241
|
+
return _history_page_response(request, self, project, task.history)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class ProjectHistoryListView(APIView):
|
|
245
|
+
"""Paginated change history for a project (project-level fields only).
|
|
246
|
+
|
|
247
|
+
GET /api/v1/projects/{project_pk}/history/
|
|
248
|
+
|
|
249
|
+
At most ``_MAX_HISTORY_ROWS`` records are materialized; ``count_truncated``
|
|
250
|
+
in the response is ``true`` when the cap was hit.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
# IsProjectNotArchived is deliberately omitted: history is a read-only audit
|
|
254
|
+
# surface that must stay accessible after a project is archived. Do not add it
|
|
255
|
+
# "for consistency" — archived projects still need their audit trail readable.
|
|
256
|
+
permission_classes = [IsAuthenticated, IsProjectMember]
|
|
257
|
+
|
|
258
|
+
@extend_schema(
|
|
259
|
+
# Same class of defect as #2583's two named instances: a plain APIView that
|
|
260
|
+
# builds its paginator inline is invisible to drf-spectacular's auto-wrap
|
|
261
|
+
# heuristic, and with no annotation at all the operation shipped a 200 with
|
|
262
|
+
# no response content whatsoever — a generated SDK gets an untyped body for
|
|
263
|
+
# a paginated endpoint. Declare the envelope, including the non-standard
|
|
264
|
+
# ``count_truncated`` flag the handler grafts onto it below.
|
|
265
|
+
responses={
|
|
266
|
+
200: inline_serializer(
|
|
267
|
+
name="ProjectHistoryPage",
|
|
268
|
+
fields={
|
|
269
|
+
"count": drf_serializers.IntegerField(),
|
|
270
|
+
"next": drf_serializers.URLField(allow_null=True),
|
|
271
|
+
"previous": drf_serializers.URLField(allow_null=True),
|
|
272
|
+
"results": HistoryRecordSerializer(many=True),
|
|
273
|
+
"count_truncated": drf_serializers.BooleanField(),
|
|
274
|
+
},
|
|
275
|
+
)
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
279
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
280
|
+
self.check_object_permissions(request, project)
|
|
281
|
+
return _history_page_response(request, self, project, project.history)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class ProjectChangelogView(APIView):
|
|
285
|
+
"""Unified newest-first "what changed" stream for a whole project (ADR-0201).
|
|
286
|
+
|
|
287
|
+
GET /api/v1/projects/{project_pk}/changelog/
|
|
288
|
+
?since=&object_type=&change_type=&user=&cursor=&page_size=
|
|
289
|
+
|
|
290
|
+
Aggregates every project-scoped ``django-simple-history`` table (Task, Sprint,
|
|
291
|
+
Risk, Project, Dependency, TaskRecurrenceRule, and the three policy singletons)
|
|
292
|
+
into one stream with a **stable keyset cursor** — pass the response's
|
|
293
|
+
``next_cursor`` back as ``cursor`` to page. The cursor is opaque and safe to
|
|
294
|
+
persist; a malformed cursor returns 400.
|
|
295
|
+
|
|
296
|
+
Permissions: any project member (Viewer+) may read (see ADR-0201 — every
|
|
297
|
+
source's live GET is Viewer+, so membership is a sufficient row gate).
|
|
298
|
+
``history_user`` is returned only to Owner/Admin (``role >= Role.ADMIN``); the
|
|
299
|
+
``user=`` filter is likewise honored only for those callers, so the feed can
|
|
300
|
+
never be turned into a per-person activity tracker by a lower-privilege reader.
|
|
301
|
+
|
|
302
|
+
Query params:
|
|
303
|
+
since ISO-8601 datetime; inclusive lower bound on the change time.
|
|
304
|
+
object_type Comma-separated source keys (task, sprint, risk, dependency,
|
|
305
|
+
project, task_recurrence, guardrail_policy,
|
|
306
|
+
signal_privacy_policy, decisions_policy). Default: all.
|
|
307
|
+
change_type Comma-separated: created, updated, deleted. Default: all.
|
|
308
|
+
user A history_user id (Owner/Admin only; ignored otherwise).
|
|
309
|
+
cursor Opaque keyset cursor from a prior response's ``next_cursor``.
|
|
310
|
+
page_size 1..100 (default 50).
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
# IsProjectNotArchived is deliberately omitted: history is a read-only audit
|
|
314
|
+
# surface that must stay accessible after a project is archived (matching the
|
|
315
|
+
# per-object history views above).
|
|
316
|
+
permission_classes = [IsAuthenticated, IsProjectMember]
|
|
317
|
+
|
|
318
|
+
@extend_schema(
|
|
319
|
+
summary="Unified project changelog (aggregated, filterable, cursor-paginated)",
|
|
320
|
+
parameters=[
|
|
321
|
+
OpenApiParameter(
|
|
322
|
+
"since", str, description="Inclusive ISO-8601 lower bound on the change time."
|
|
323
|
+
),
|
|
324
|
+
OpenApiParameter(
|
|
325
|
+
"object_type",
|
|
326
|
+
str,
|
|
327
|
+
description=(
|
|
328
|
+
"Comma-separated source keys: "
|
|
329
|
+
f"{', '.join(changelog.object_type_choices())}. Default: all."
|
|
330
|
+
),
|
|
331
|
+
),
|
|
332
|
+
OpenApiParameter(
|
|
333
|
+
"change_type",
|
|
334
|
+
str,
|
|
335
|
+
description="Comma-separated change types: created, updated, deleted.",
|
|
336
|
+
),
|
|
337
|
+
OpenApiParameter(
|
|
338
|
+
"user", str, description="Filter to one history_user id (Owner/Admin only)."
|
|
339
|
+
),
|
|
340
|
+
OpenApiParameter(
|
|
341
|
+
"cursor",
|
|
342
|
+
str,
|
|
343
|
+
description=(
|
|
344
|
+
"Opaque keyset cursor from a prior response's next_cursor. "
|
|
345
|
+
"Stable across concurrent writes; persistable; 400 if malformed."
|
|
346
|
+
),
|
|
347
|
+
),
|
|
348
|
+
OpenApiParameter("page_size", int, description="Page size (1..100, default 50)."),
|
|
349
|
+
],
|
|
350
|
+
responses={200: OpenApiResponse(ChangelogResponseSerializer)},
|
|
351
|
+
)
|
|
352
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
353
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
354
|
+
self.check_object_permissions(request, project)
|
|
355
|
+
|
|
356
|
+
since = self._parse_since(request.query_params.get("since"))
|
|
357
|
+
object_types = self._parse_object_types(request.query_params.get("object_type"))
|
|
358
|
+
change_types = self._parse_change_types(request.query_params.get("change_type"))
|
|
359
|
+
page_size = self._parse_page_size(request.query_params.get("page_size"))
|
|
360
|
+
|
|
361
|
+
cursor_raw = request.query_params.get("cursor")
|
|
362
|
+
cursor = changelog.ChangelogCursor.decode(cursor_raw) if cursor_raw else None
|
|
363
|
+
|
|
364
|
+
# history_user visibility gates both the field and the user= filter: only
|
|
365
|
+
# Owner/Admin may see who made a change, so only they may filter by it.
|
|
366
|
+
hide_user = not _caller_can_see_user(request, project)
|
|
367
|
+
user_id = None if hide_user else self._parse_user(request.query_params.get("user"))
|
|
368
|
+
|
|
369
|
+
entries, next_cursor = changelog.build_project_changelog(
|
|
370
|
+
project,
|
|
371
|
+
diff_fn=functools.partial(_compute_diffs, object_scoped=False),
|
|
372
|
+
cursor=cursor,
|
|
373
|
+
since=since,
|
|
374
|
+
object_types=object_types,
|
|
375
|
+
change_types=change_types,
|
|
376
|
+
user_id=user_id,
|
|
377
|
+
page_size=page_size,
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
serializer = ChangelogResponseSerializer(
|
|
381
|
+
{
|
|
382
|
+
"results": entries,
|
|
383
|
+
"next_cursor": next_cursor.encode() if next_cursor is not None else None,
|
|
384
|
+
},
|
|
385
|
+
context={"hide_user": hide_user},
|
|
386
|
+
)
|
|
387
|
+
return Response(serializer.data)
|
|
388
|
+
|
|
389
|
+
@staticmethod
|
|
390
|
+
def _parse_user(raw: str | None) -> int | None:
|
|
391
|
+
# The user model's PK is an integer AutoField, so a non-integer value must
|
|
392
|
+
# yield a clean 400 rather than a 500 when the ORM casts it at query time.
|
|
393
|
+
if not raw:
|
|
394
|
+
return None
|
|
395
|
+
try:
|
|
396
|
+
return int(raw)
|
|
397
|
+
except (TypeError, ValueError) as err:
|
|
398
|
+
raise DRFValidationError({"user": "Must be a user id (integer)."}) from err
|
|
399
|
+
|
|
400
|
+
@staticmethod
|
|
401
|
+
def _parse_since(raw: str | None) -> Any:
|
|
402
|
+
if not raw:
|
|
403
|
+
return None
|
|
404
|
+
parsed = parse_datetime(raw)
|
|
405
|
+
if parsed is None:
|
|
406
|
+
raise DRFValidationError({"since": f"Invalid datetime '{raw}' (expected ISO 8601)."})
|
|
407
|
+
if timezone.is_naive(parsed):
|
|
408
|
+
parsed = timezone.make_aware(parsed, timezone.get_current_timezone())
|
|
409
|
+
return parsed
|
|
410
|
+
|
|
411
|
+
@staticmethod
|
|
412
|
+
def _parse_object_types(raw: str | None) -> set[str] | None:
|
|
413
|
+
if not raw:
|
|
414
|
+
return None
|
|
415
|
+
requested = {t.strip() for t in raw.split(",") if t.strip()}
|
|
416
|
+
valid = set(changelog.object_type_choices())
|
|
417
|
+
unknown = requested - valid
|
|
418
|
+
if unknown:
|
|
419
|
+
raise DRFValidationError(
|
|
420
|
+
{"object_type": f"Unknown object type(s): {', '.join(sorted(unknown))}."}
|
|
421
|
+
)
|
|
422
|
+
return requested
|
|
423
|
+
|
|
424
|
+
@staticmethod
|
|
425
|
+
def _parse_change_types(raw: str | None) -> set[str] | None:
|
|
426
|
+
if not raw:
|
|
427
|
+
return None
|
|
428
|
+
requested = {t.strip() for t in raw.split(",") if t.strip()}
|
|
429
|
+
unknown = requested - changelog.CHANGE_TYPES
|
|
430
|
+
if unknown:
|
|
431
|
+
raise DRFValidationError(
|
|
432
|
+
{"change_type": f"Unknown change type(s): {', '.join(sorted(unknown))}."}
|
|
433
|
+
)
|
|
434
|
+
return requested
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _parse_page_size(raw: str | None) -> int:
|
|
438
|
+
if not raw:
|
|
439
|
+
return changelog.DEFAULT_PAGE_SIZE
|
|
440
|
+
try:
|
|
441
|
+
return int(raw)
|
|
442
|
+
except (TypeError, ValueError) as err:
|
|
443
|
+
raise DRFValidationError({"page_size": "Must be an integer."}) from err
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""AppConfig for the idempotency app — HTTP Idempotency-Key request dedup."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IdempotencyConfig(AppConfig):
|
|
9
|
+
name = "trueppm_api.apps.idempotency"
|
|
10
|
+
verbose_name = "Idempotency"
|
|
11
|
+
default_auto_field = "django.db.models.BigAutoField"
|