(backend/frontend) move access/invitation cleanup from frontend to backend on document move#2223
Conversation
Access and invitation cleanup on document move is now handled atomically by the backend. The frontend no longer needs to fetch and delete accesses/invitations after a successful move. Signed-off-by: Mohamed El Amine BOUKERFA <boukerfa.ma@gmail.com>
WalkthroughThe backend DocumentViewSet.move now materializes owner_accesses into a list when moving under a root target, detects permission-scope changes (root status, becoming a sibling root, or moving between trees), and—on scope change—deletes direct document.accesses and pending document.invitations inside the same Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/backend/core/api/viewsets.py`:
- Around line 965-971: Materialize the owner_accesses queryset before deleting
direct accesses: before calling document.accesses.all().delete() (and
document.invitations.all().delete()), evaluate owner_accesses into a concrete
list (e.g. owner_accesses = list(owner_accesses)) so the later owner-recreation
loop can iterate over the preserved items; update the code that computes
owner_accesses to materialize it and then proceed with the deletes and the owner
recreation logic.
- Around line 969-971: The cleanup condition incorrectly only compares current
roots; instead compute the prospective root after the move and use that to
decide whether to drop direct accesses/invitations. Change the logic around
document.is_root(), document.get_root(), and target_document.get_root() to
determine the new root post-move (e.g., if moving as a child use
target_document.get_root(), if moving as a sibling use
target_document.get_parent() and its root or None if that parent is root) and
then run document.accesses.all().delete() and
document.invitations.all().delete() when document.is_root() or
document.get_root() != prospective_root (i.e., when the root membership changes
after the move).
In `@src/backend/core/tests/documents/test_api_documents_move.py`:
- Around line 594-620: The test currently fails before the deletion block
because the target lookup uses a random UUID; change it to use a real target
document and force Document.move to raise after the deletes so rollback can be
observed. Concretely: create a real target_document (e.g. via
factories.DocumentFactory()) and pass its id in the POST, and monkeypatch or
temporarily stub Document.move (the method on the Document model) to raise an
exception when called; then call client.post on the move endpoint and assert the
response indicates failure and that document.accesses.count() and
document.invitations.count() remain unchanged (verifying the deletion roll
back).
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 56acc976-7a4f-4750-b14f-c62dbde5d92e
📒 Files selected for processing (5)
src/backend/core/api/viewsets.pysrc/backend/core/tests/documents/test_api_documents_move.pysrc/frontend/apps/impress/src/features/docs/doc-management/api/useMoveDoc.tsxsrc/frontend/apps/impress/src/features/docs/docs-grid/components/DocGridContentList.tsxsrc/frontend/apps/impress/src/features/docs/docs-grid/components/DocMoveModal.tsx
0c0f025 to
99bfe78
Compare
When a document is moved outside its current permission scope (root document, cross-tree move, or promotion to root), its direct accesses and pending invitations are now deleted server-side within the same atomic transaction as the move itself. This ensures consistency: if the move fails, deletions are rolled back. Signed-off-by: Mohamed El Amine BOUKERFA <boukerfa.ma@gmail.com>
99bfe78 to
ffe8b59
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/backend/core/tests/documents/test_api_documents_move.py`:
- Line 697: Add a short comment above the monkeypatch line explaining the test's
contract: that the API view under test invokes models.Document.move (inherited
from treebeard.MP_Node) and therefore the test injects failure via
monkeypatch.setattr(models.Document, "move", failing_move) to exercise rollback;
note that if the view later calls a differently-named method the test intent
must be updated. This documents the dependency on models.Document.move and the
purpose of the failing_move injection so future refactors don't silently break
the test.
🪄 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: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9403005d-2240-4fea-9cae-5a823958856d
📒 Files selected for processing (2)
src/backend/core/api/viewsets.pysrc/backend/core/tests/documents/test_api_documents_move.py
| def failing_move(self, *args, **kwargs): | ||
| raise RuntimeError("Simulated move failure for atomicity test") | ||
|
|
||
| monkeypatch.setattr(models.Document, "move", failing_move) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm that the move method invoked by the viewset is indeed Document.move
# (inherited from treebeard) and not a custom override or a differently-named method.
ast-grep --pattern $'class Document($$$):
$$$
def move($$$):
$$$
$$$'
# Also confirm the viewset call site
rg -nP --type=py -C1 '\bdocument\.move\s*\(' src/backend/core/api/viewsets.pyRepository: suitenumerique/docs
Length of output: 130
🏁 Script executed:
# Check Document class definition and inheritance
rg -nP --type=py 'class Document\(' src/backend/core/models.py | head -20Repository: suitenumerique/docs
Length of output: 103
🏁 Script executed:
# Read the test code around line 697 for context
head -n 710 src/backend/core/tests/documents/test_api_documents_move.py | tail -n 30Repository: suitenumerique/docs
Length of output: 1253
Consider documenting the move method contract assumption in the test.
The monkeypatch target models.Document.move is correct—Document inherits move from treebeard's MP_Node and does not override it. The test properly exercises the rollback path by injecting a failure at the inherited method level. To future-proof this test, add a brief comment clarifying that the API view ultimately calls Document.move (inherited from treebeard) and document the dependency contract, so a future refactor that redirects to a differently-named method won't silently break the test's intent.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/backend/core/tests/documents/test_api_documents_move.py` at line 697, Add
a short comment above the monkeypatch line explaining the test's contract: that
the API view under test invokes models.Document.move (inherited from
treebeard.MP_Node) and therefore the test injects failure via
monkeypatch.setattr(models.Document, "move", failing_move) to exercise rollback;
note that if the view later calls a differently-named method the test intent
must be updated. This documents the dependency on models.Document.move and the
purpose of the failing_move injection so future refactors don't silently break
the test.
|
The backend part is ok for me. |
Purpose
Move the access and invitation cleanup logic from the frontend to the backend during document move operations.
Problems with the current approach:
an inconsistent state.
breaking the inheritance model.
Screen.Recording.2026-04-16.at.17.29.32.mov
Proposal