SACGF / SACGF/variantgrid

ClassificationGrouping not cleaned up when classifications are deleted (pre_delete recalc runs before delete)

Open
#1,776 0 comments 0 reactions 1 assignee Assigned to @TheMadBug View on GitHub
Dominant language
Python
Stars
30
Forks
3
Avg merge
9h 28m
Merged PRs (30d)
42

Description

🤖 Written by Claude

## Symptom

After hard-deleting classifications, their `ClassificationGrouping` rows remain in the grouping grid with no linked classifications, and are never cleaned up (there is no nightly/scheduled sweep — grouping recalculation is entirely signal-driven).

## Cause

The delete handler in `classification/signals/classification_hooks_grouping.py` (`deleting_classification`) is on **`pre_delete`** and recalculates synchronously, *before* the classification row is actually deleted:

1. `pre_delete` fires; the handler calls `entry.dirty_up()` then `_instant_undirty_check()`.
2. `ClassificationGrouping.update()` runs right then — but the `ClassificationGroupingEntry` still exists (it is only removed by CASCADE when the classification row is deleted). So `classification_modifications` still includes the doomed classification, the grouping re-saves with `dirty=False`, and the "no classifications, time to die" branch (`ClassificationGrouping.update()`, `classification_grouping.py` ~line 469) never runs.
3. The delete commits, the entry cascades away, leaving a stale grouping with `dirty=False` that nothing ever revisits.

So the signal exists but is effectively a no-op for deletes — it recalculates the pre-delete state and then marks the grouping as up to date.

## Suggested fix

Keep the handler on `pre_delete` (by `post_delete` the entry has already been cascaded away and can't be found), but make the recalc not see the dying entry:

- In the handler, explicitly delete the `ClassificationGroupingEntry` and `dirty_up()` its grouping *before* calling `_instant_undirty_check()`, so the recount excludes the classification and empty groupings self-delete. This mirrors what `assign_grouping_for_classification` already does in its "no allele" branch.
- Alternatively, register `transaction.on_commit(ClassificationGrouping.update_all_dirty)` instead of recalculating inline — also safer if the surrounding delete transaction rolls back.

## Cleanup of existing orphans

`python3 manage.py classification_groupings --refresh` re-runs `update()` on every grouping, so empty ones delete themselves.

Note: `AlleleOriginGrouping.update()` never deletes itself when empty (its aggregation logic is mostly commented out with a FIX-ME), so empty allele-origin/allele grouping shells will still linger after the refresh.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.