Skip to content

perf(api): harden compliance-overview ingest & partition table#11870

Closed
pedrooot wants to merge 3 commits into
masterfrom
improve-compliance-overviews
Closed

perf(api): harden compliance-overview ingest & partition table#11870
pedrooot wants to merge 3 commits into
masterfrom
improve-compliance-overviews

Conversation

@pedrooot

@pedrooot pedrooot commented Jul 7, 2026

Copy link
Copy Markdown
Member

Description

On every scan we delete the scan's existing rows and re-insert the whole set, streaming them into compliance_requirements_overviews with back-to-back COPY statements that take 13–15s each. On a plain, ever-growing table that means constant commit/fsync waits, lock contention, and a lot of autovacuum work chasing the dead tuples the delete-then-reinsert leaves behind. Under load it's enough to tip the writer over.

What changed

Ingest (tasks/jobs/scan.py). The COPY batch size is now configurable via DJANGO_COMPLIANCE_COPY_BATCH_SIZE (default dropped from 10000 to 5000), with an optional pause between batches via DJANGO_COMPLIANCE_COPY_THROTTLE_SECONDS (default 0, so behaviour is unchanged unless someone turns it up). Smaller batches give the writer room to checkpoint and vacuum between flushes, and ops can tune both without a deploy. We also reuse a single admin connection across all batches instead of reconnecting per batch — the tenant GUC is re-applied inside each batch's transaction since SET_CONFIG_QUERY is is_local=TRUE. And on a scan's first run we skip the pre-insert DELETE entirely (there's nothing to delete), so we don't open an empty write transaction or generate dead tuples for no reason.

Partitioning. compliance_requirements_overviews is now a partitioned table, RANGE by its UUIDv7 id, the same way findings works. That lets old scans' rows age out with DROP PARTITION instead of DELETE + CASCADE, which is where most of the autovacuum churn came from. The row generator emits uuid7() ids now so new rows land in the right monthly partition (uuid4 would always fall into default). The old 5-column unique constraint is gone — once the partition key id gets added to it, it doesn't mean anything, and per-scan dedup is already handled by the delete-then-reinsert, exactly like findings does.

Migration 0098. Converts the table in place, without copying any data. It's a SeparateDatabaseAndState: the state side mirrors the model changes, and the database side renames the existing table and re-attaches it as the default partition of a new partitioned parent (ATTACH is metadata-only, no row rewrite). The parent FKs are added while it's still empty and the existing index is attached as-is, so nothing gets re-scanned or re-indexed. RLS is re-established on both the parent and the default partition through the project's RowLevelSecurityConstraint.

Steps to review

Please add a detailed description of how to review this PR.

Checklist

Community Checklist
  • This feature/issue is listed in here or roadmap.prowler.com
  • Is it assigned to me, if not, request it via the issue/feature in here or Prowler Community Slack

SDK/CLI

  • Are there new checks included in this PR? Yes / No
    • If so, do we need to update permissions for the provider? Please review this carefully.

UI

  • All issue/task requirements work as expected on the UI
  • If this PR adds or updates npm dependencies, include package-health evidence (maintenance, popularity, known vulnerabilities, license, release age) and explain why existing/native alternatives are insufficient.
  • Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
  • Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
  • Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
  • Ensure new entries are added to CHANGELOG.md, if applicable.

API

  • All issue/task requirements work as expected on the API
  • Endpoint response output (if applicable)
  • EXPLAIN ANALYZE output for new/modified queries or indexes (if applicable)
  • Performance test results (if applicable)
  • Any other relevant evidence of the implementation (if applicable)
  • Verify if API specs need to be regenerated.
  • Check if version updates are required (e.g., specs, uv, etc.).
  • Ensure new entries are added to CHANGELOG.md, if applicable.

License

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Summary by CodeRabbit

  • New Features

    • Compliance requirement overviews now use faster, range-partitioned storage with more scalable IDs.
    • Added separate configuration options to tune how these records are partitioned and retained (partition months, partition count, and optional max-age/indefinite retention).
  • Bug Fixes

    • Improved compliance overview ingestion to reduce memory pressure by using batched COPY with optional throttling and safer re-run behavior that avoids unnecessary empty deletes.
    • Made persistence more resilient by falling back to ORM bulk insert for any failed batch while keeping successful batches committed.

@pedrooot pedrooot requested a review from a team as a code owner July 7, 2026 14:29
@github-actions github-actions Bot added component/api review-django-migrations This PR contains changes in Django migrations labels Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

✅ All necessary CHANGELOG.md files have been updated.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

No Conflicts

No conflict markers, and the branch merges cleanly into its base.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR converts compliance_requirements_overviews into a RANGE-partitioned PostgreSQL table keyed on UUIDv7 ids, wires new partitioning configuration and settings, and hardens the scan.py compliance-overview COPY ingest path with a reused admin connection, configurable batching/throttling, per-batch commit/rollback with ORM fallback, and a conditional DELETE on re-runs. Tests and changelog are updated accordingly.

Changes

Partitioning and hardened ingest

Layer / File(s) Summary
Partitioned model and migration
api/src/backend/api/models.py, api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py
ComplianceRequirementOverview becomes a range-partitioned model on UUIDv7 id, removing the old multi-column unique constraint and adding a default-partition RLS constraint; migration 0098 converts the existing table to a partitioned parent with a default partition and DEFERRABLE FKs, plus a reverse operation.
Partition manager and settings wiring
api/src/backend/api/partitions.py, api/src/backend/config/settings/partitions.py
Registers ComplianceRequirementOverview in the partitioning manager with a UUIDv7 monthly strategy and RLS statements, backed by new environment-configurable partition month/count/max-age settings.
Hardened batched COPY ingest
api/src/backend/tasks/jobs/scan.py
Reworks compliance-overview persistence to lazily reuse a single admin connection via ExitStack, stream configurable-size batches with optional throttling, roll back and fall back to ORM bulk_create per failed batch, generate UUIDv7 row ids, and only DELETE existing rows when present on re-run.
Test updates and changelog
api/src/backend/tasks/tests/test_scan.py, api/CHANGELOG.md
Updates COPY-related tests to use local connection/cursor mocks or patched psycopg_connection and assert the new (connection, tenant_id, rows) call signature; adds a changelog entry documenting the ingest hardening.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • prowler-cloud/prowler#11591: Both PRs refactor api/src/backend/tasks/jobs/scan.py's compliance-overviews COPY persistence pipeline, directly overlapping with this PR's batching/hardening changes.

Suggested reviewers: josema-xyz, Davidm4r, alejandrobailo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: hardening compliance-overview ingest and partitioning the table.
Description check ✅ Passed The description covers context, the main changes, and checklist items, but the Steps to review section is still a placeholder.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve-compliance-overviews

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🔒 Container Security Scan

Image: prowler-api:32e2f61
Last scan: 2026-07-07 14:42:19 UTC

✅ No Vulnerabilities Detected

The container image passed all security checks. No known CVEs were found.

📋 Resources:

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.80488% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.81%. Comparing base (ad04e69) to head (de09752).
⚠️ Report is 7 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11870      +/-   ##
==========================================
- Coverage   93.83%   93.81%   -0.02%     
==========================================
  Files         263      264       +1     
  Lines       38692    38701       +9     
==========================================
+ Hits        36305    36306       +1     
- Misses       2387     2395       +8     
Flag Coverage Δ
api 93.81% <87.80%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
prowler ∅ <ø> (∅)
api 93.81% <87.80%> (-0.02%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/src/backend/tasks/tests/test_scan.py (1)

2887-2923: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing test coverage for multi-batch connection reuse/throttling.

_persist_compliance_requirement_rows reuses a single admin connection across batches and supports throttle_seconds, but every test here only exercises a single batch. There's no assertion that psycopg_connection is opened exactly once across multiple batches, or that throttling is applied between batches — both are core claims of this hardening work per the changelog entry.

Consider adding a test with batch_size smaller than the row count to assert mock_psycopg_connection.assert_called_once() across batches, plus a throttle test asserting time.sleep is invoked between batches.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/backend/tasks/tests/test_scan.py` around lines 2887 - 2923, The
current `_persist_compliance_requirement_rows` test only covers a single
successful batch and does not verify the new multi-batch behavior. Add a test
that calls `_persist_compliance_requirement_rows` with more rows than the
configured batch size so it processes multiple batches, then assert
`psycopg_connection` is opened only once and `_copy_compliance_requirement_rows`
is invoked for each batch. Also add a separate throttling test for the same
function that patches `time.sleep` and verifies it is called between batches
when `throttle_seconds` is set.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py`:
- Line 139: The reverse migration in the partition cleanup logic is issuing an
invalid ALTER INDEX ... DETACH PARTITION statement, which will fail in
PostgreSQL. Update the migration rollback code around the partition/index
handling to remove the explicit index detach and only detach the table
partition; the attached index will be detached automatically. Use the
migration’s reverse SQL block that references INDEX and the _default partition
to make the change.

---

Outside diff comments:
In `@api/src/backend/tasks/tests/test_scan.py`:
- Around line 2887-2923: The current `_persist_compliance_requirement_rows` test
only covers a single successful batch and does not verify the new multi-batch
behavior. Add a test that calls `_persist_compliance_requirement_rows` with more
rows than the configured batch size so it processes multiple batches, then
assert `psycopg_connection` is opened only once and
`_copy_compliance_requirement_rows` is invoked for each batch. Also add a
separate throttling test for the same function that patches `time.sleep` and
verifies it is called between batches when `throttle_seconds` is set.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8ccbe2a7-46fe-4392-a7eb-3628d4d70fdb

📥 Commits

Reviewing files that changed from the base of the PR and between 76a2d7b and f0238a8.

📒 Files selected for processing (7)
  • api/CHANGELOG.md
  • api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py
  • api/src/backend/api/models.py
  • api/src/backend/api/partitions.py
  • api/src/backend/config/settings/partitions.py
  • api/src/backend/tasks/jobs/scan.py
  • api/src/backend/tasks/tests/test_scan.py

with cursor() as cur:
# Detach the default partition (keeps its own pkey/index/FKs) and drop the
# partitioned parent, then rename the default back to the original table.
cur.execute(f"ALTER INDEX {INDEX} DETACH PARTITION {INDEX}_default;")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the migration and nearby definitions
FILE="api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py"

echo "== file size =="
wc -l "$FILE"

echo "== outline (if available) =="
ast-grep outline "$FILE" --view expanded || true

echo "== relevant lines around reverse migration =="
sed -n '110,170p' "$FILE" | cat -n

echo "== search for RowLevelSecurityConstraint deconstruct / serialization =="
rg -n "class RowLevelSecurityConstraint|def deconstruct|partition_name|statements|DEFAULT_RLS|ADD_CONSTRAINT|AddConstraint" api/src/backend/api -S

Repository: prowler-cloud/prowler

Length of output: 50379


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== RowLevelSecurityConstraint in api/src/backend/api/rls.py =="
sed -n '1,220p' api/src/backend/api/rls.py | cat -n

echo
echo "== Migration state/database operations in api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py =="
sed -n '160,220p' api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py | cat -n

echo
echo "== Exact AddConstraint usages for this migration =="
rg -n "0098_partition_compliance_requirement_overviews.py|AddConstraint|RemoveConstraint|SeparateDatabaseAndState|RunPython|AlterModelOptions|AlterModelManagers|RemoveConstraint|AddIndex" api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py -n -S

Repository: prowler-cloud/prowler

Length of output: 10787


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
p = Path("api/src/backend/api/rls.py")
text = p.read_text()
for needle in ["class RowLevelSecurityConstraint", "def deconstruct", "self.partition_name", "self.statements"]:
    idx = text.find(needle)
    print(f"\n--- {needle} ---")
    if idx != -1:
        start = max(0, text.rfind("\n", 0, idx-200))
        end = min(len(text), idx+800)
        print(text[start:end])
PY

Repository: prowler-cloud/prowler

Length of output: 4369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate ComplianceRequirementOverview model =="
rg -n "class ComplianceRequirementOverview|RowLevelSecurityConstraint\\(|partition_name=\"default\"|statements=\\[" api/src/backend/api/models.py api/src/backend/api/partitions.py api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py -n -S

echo
echo "== Model block around ComplianceRequirementOverview =="
python3 - <<'PY'
from pathlib import Path
text = Path("api/src/backend/api/models.py").read_text().splitlines()
# Print a window around the first occurrence of ComplianceRequirementOverview
for i, line in enumerate(text):
    if "class ComplianceRequirementOverview" in line:
        start = max(0, i - 20)
        end = min(len(text), i + 120)
        for j in range(start, end):
            print(f"{j+1:5d}: {text[j]}")
        break
PY

echo
echo "== Partition helpers around the relevant class =="
python3 - <<'PY'
from pathlib import Path
text = Path("api/src/backend/api/partitions.py").read_text().splitlines()
for i, line in enumerate(text):
    if "rls_statements" in line or "class Partition" in line:
        start = max(0, i - 25)
        end = min(len(text), i + 120)
        for j in range(start, end):
            print(f"{j+1:5d}: {text[j]}")
        print("\n---\n")
        if i > 250:
            break
PY

Repository: prowler-cloud/prowler

Length of output: 50377


🌐 Web query:

PostgreSQL ALTER INDEX DETACH PARTITION documentation

💡 Result:

In PostgreSQL, there is no direct command such as ALTER INDEX DETACH PARTITION. Instead, the detachment of an index is a side effect of detaching a partition from a table. When you detach a partition from a partitioned table using the ALTER TABLE... DETACH PARTITION command, any indexes that were attached to the parent table's indexes are automatically detached from them [1][2]. The relevant command to perform this operation is: ALTER TABLE parent_table_name DETACH PARTITION partition_name; Key technical details: 1. Relationship: While ALTER INDEX ATTACH PARTITION exists to explicitly link an index on a partition to an index on the partitioned parent table [3][4], the corresponding detachment occurs automatically during the table-level partition detachment [1][2]. 2. Behavior: Once the parent partition is detached, the detached partition remains as a standalone table, and its indexes are no longer bound to the parent's indexes [1][5]. 3. Execution: You can execute this as a standard (blocking) detachment or, in supported versions, use the CONCURRENTLY option to reduce locking requirements while detaching [1][2]. Because ALTER INDEX is effectively an alias for forms of ALTER TABLE that apply to indexes [3][4], all partition-related structural changes, including the detachment of indexes that were previously attached to a parent index, are managed through the ALTER TABLE command interface.

Citations:


Remove the invalid index detach on line 139
ALTER INDEX ... DETACH PARTITION isn’t valid PostgreSQL, so the reverse migration will fail. Detach the table partition only; its attached index is detached automatically.

🧰 Tools
🪛 OpenGrep (1.23.0)

[ERROR] 139-139: SQL query built via f-string passed to execute()/executemany(). Use parameterized queries with placeholders instead.

(coderabbit.sql-injection.python-fstring-execute)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@api/src/backend/api/migrations/0098_partition_compliance_requirement_overviews.py`
at line 139, The reverse migration in the partition cleanup logic is issuing an
invalid ALTER INDEX ... DETACH PARTITION statement, which will fail in
PostgreSQL. Update the migration rollback code around the partition/index
handling to remove the explicit index detach and only detach the table
partition; the attached index will be detached automatically. Use the
migration’s reverse SQL block that references INDEX and the _default partition
to make the change.

Source: Linters/SAST tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/src/backend/tasks/tests/test_scan.py (2)

2444-2445: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove vestigial MainRouter.admin_db patch.

These tests now pass connection explicitly to _copy_compliance_requirement_rows, which (per the current implementation) no longer looks up MainRouter.admin_db. The with patch.object(MainRouter, "admin_db", "admin"): wrapper is leftover from the pre-refactor design and is dead weight in each of these tests.

🧹 Example cleanup (repeat for each occurrence)
-        with patch.object(MainRouter, "admin_db", "admin"):
-            _copy_compliance_requirement_rows(connection, str(row["tenant_id"]), [row])
+        _copy_compliance_requirement_rows(connection, str(row["tenant_id"]), [row])

Also applies to: 2592-2593, 2660-2661, 2704-2705, 2753-2754, 2793-2797, 2833-2837, 2870-2871

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/backend/tasks/tests/test_scan.py` around lines 2444 - 2445, Remove
the leftover MainRouter.admin_db patch in the tests around
_copy_compliance_requirement_rows, since the helper now uses the explicitly
passed connection and no longer depends on MainRouter.admin_db. Update each
affected test case to call _copy_compliance_requirement_rows directly without
the with patch.object(MainRouter, "admin_db", "admin") wrapper, keeping the
existing connection and row setup unchanged.

2412-2870: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate connection/cursor mock boilerplate across tests.

The connection = MagicMock(); cursor = MagicMock(); cursor_context = MagicMock(); ...; connection.cursor.return_value = cursor_context setup is repeated verbatim in at least 8 test methods. A pytest fixture returning (connection, cursor) would remove this duplication and make future signature changes easier to maintain.

♻️ Example fixture
`@pytest.fixture`
def mock_copy_connection():
    connection = MagicMock()
    cursor = MagicMock()
    cursor_context = MagicMock()
    cursor_context.__enter__.return_value = cursor
    cursor_context.__exit__.return_value = False
    connection.cursor.return_value = cursor_context
    return connection, cursor
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/src/backend/tasks/tests/test_scan.py` around lines 2412 - 2870, The
connection/cursor setup is duplicated across multiple compliance COPY tests,
making the suite noisy and harder to maintain. Extract the repeated MagicMock
context-manager wiring into a shared pytest fixture (for example, one returning
the connection and cursor used by tests like
test_copy_compliance_requirement_rows_streams_csv and
test_copy_compliance_requirement_rows_multiple_rows), then update each test to
use that fixture instead of recreating the same boilerplate inline. Keep the
existing cursor.copy_expert and cursor.execute assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@api/src/backend/tasks/tests/test_scan.py`:
- Around line 2444-2445: Remove the leftover MainRouter.admin_db patch in the
tests around _copy_compliance_requirement_rows, since the helper now uses the
explicitly passed connection and no longer depends on MainRouter.admin_db.
Update each affected test case to call _copy_compliance_requirement_rows
directly without the with patch.object(MainRouter, "admin_db", "admin") wrapper,
keeping the existing connection and row setup unchanged.
- Around line 2412-2870: The connection/cursor setup is duplicated across
multiple compliance COPY tests, making the suite noisy and harder to maintain.
Extract the repeated MagicMock context-manager wiring into a shared pytest
fixture (for example, one returning the connection and cursor used by tests like
test_copy_compliance_requirement_rows_streams_csv and
test_copy_compliance_requirement_rows_multiple_rows), then update each test to
use that fixture instead of recreating the same boilerplate inline. Keep the
existing cursor.copy_expert and cursor.execute assertions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c84382ac-d551-44cb-b3f9-2ccc74015526

📥 Commits

Reviewing files that changed from the base of the PR and between f0238a8 and de09752.

📒 Files selected for processing (1)
  • api/src/backend/tasks/tests/test_scan.py

@pedrooot pedrooot marked this pull request as draft July 7, 2026 15:25
@pedrooot

pedrooot commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

closed by #11875

@pedrooot pedrooot closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/api review-django-migrations This PR contains changes in Django migrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant