Skip to content

Added action RecreateSession#36

Open
chris-adam wants to merge 3 commits into
mainfrom
PARAF-420/recreate_session
Open

Added action RecreateSession#36
chris-adam wants to merge 3 commits into
mainfrom
PARAF-420/recreate_session

Conversation

@chris-adam
Copy link
Copy Markdown
Contributor

@chris-adam chris-adam commented May 5, 2026

Summary by CodeRabbit

  • New Features

    • Added a "Recreate Session" admin action for non-draft sessions with permission checks, confirmation prompt, and an actions button in session lists.
  • Bug Fixes

    • Improved session lookup so items linked to multiple sessions are correctly reported.
  • Tests

    • Added tests for session recreation behavior and extended utilities tests for multi-session scenarios.
  • Documentation

    • Added changelog entry for the new action.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a RecreateSession admin action: new RecreateSessionView that builds a fresh session from an existing non-draft session (rebuilds signers, copies files, audits, redirects), UI wiring and ZCML registration, tests, CHANGES entry, and an adjusted get_sessions_for implementation.

Changes

Session Recreation Feature

Layer / File(s) Summary
Data Shape / Utilities
src/imio/esign/utils.py
get_sessions_for() now scans all sessions and returns those whose files reference the given context_uid, allowing an item to belong to multiple sessions.
Core Implementation
src/imio/esign/browser/actions.py
New RecreateSessionView added: permission guard (may_recreate_session), validate source session (exists, not draft), reconstruct signer data, call create_session, copy files, set _new_session_id, audit the action, set info message and redirect.
UI Integration
src/imio/esign/browser/table.py
ActionsColumn.renderCell extended to show a "recreate session" admin button for non-draft sessions when permitted (confirmation prompt, link to esign-session-recreate).
Configuration / Registration
src/imio/esign/browser/configure.zcml
Registered esign-session-recreate browser page for RecreateSessionView, exposing may_recreate_session and using zope2.View permission.
Tests
src/imio/esign/tests/test_actions.py, src/imio/esign/tests/test_utils.py
New TestRecreateSessionView with permission and workflow tests (including no-merge semantics); test_get_sessions_for extended to cover an item belonging to multiple sessions.
Changelog
CHANGES.rst
Added unreleased 1.0b8 entry: "Added action RecreateSession." attributed to [chris-adam].

Sequence Diagram

sequenceDiagram
    actor Admin
    participant RecreateSessionView
    participant SessionStorage
    participant FileManager
    participant AuditLog
    participant UI

    Admin->>RecreateSessionView: Click "Recreate Session" (confirm)
    RecreateSessionView->>RecreateSessionView: may_recreate_session (permission)
    RecreateSessionView->>SessionStorage: Fetch source session
    RecreateSessionView->>RecreateSessionView: Validate state != "draft" and build signer data
    RecreateSessionView->>SessionStorage: create_session(new session)
    RecreateSessionView->>FileManager: copy files from old -> new session
    RecreateSessionView->>AuditLog: record recreation event
    RecreateSessionView->>UI: set info message and redirect to paraphe page
    UI->>Admin: Navigate to new session paraphe view
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • IMIO/imio.esign#15: Modifies session-listing logic and get_sessions_for to support multiple sessions per context.
  • IMIO/imio.esign#7: Adds conditional admin action rendering in ActionsColumn, similar permission/visibility patterns.

Suggested reviewers

  • gbastien
  • sgeulette

"I hopped along the session trail tonight,
signers gathered, files tucked neat and tight,
a fresh ID born where old one used to be,
audit notes whisper, redirect sets it free,
the rabbit dances—recreate with delight!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added action RecreateSession' directly matches the main change: adding a new RecreateSessionView action to recreate esign sessions, which is implemented across multiple files including actions.py, configure.zcml, and table.py.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch PARAF-420/recreate_session

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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

@coveralls
Copy link
Copy Markdown

coveralls commented May 5, 2026

Coverage Report for CI Build 26227542277

Coverage increased (+0.2%) to 84.874%

Details

  • Coverage increased (+0.2%) from the base build.
  • Patch coverage: 4 uncovered changes across 1 file (44 of 48 lines covered, 91.67%).
  • 1 coverage regression across 1 file.

Uncovered Changes

File Changed Covered %
src/imio/esign/browser/table.py 4 0 0.0%

Coverage Regressions

1 previously-covered line in 1 file lost coverage.

File Lines Losing Coverage Coverage
src/imio/esign/utils.py 1 94.52%

Coverage Stats

Coverage Status
Relevant Lines: 1309
Covered Lines: 1111
Line Coverage: 84.87%
Coverage Strength: 0.85 hits per line

💛 - Coveralls

@chris-adam chris-adam force-pushed the PARAF-420/recreate_session branch 4 times, most recently from b5ed49f to 65a4670 Compare May 6, 2026 08:40
@chris-adam chris-adam marked this pull request as ready for review May 6, 2026 08:45
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/imio/esign/browser/actions.py (1)

17-18: 💤 Low value

Unused imports.

PersistentList and PersistentMapping are imported but not used in this file. The create_session function handles persistent data structures internally.

♻️ Remove unused imports
 from imio.helpers.security import check_zope_admin
-from persistent.list import PersistentList
-from persistent.mapping import PersistentMapping
 from plone import api
🤖 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 `@src/imio/esign/browser/actions.py` around lines 17 - 18, The file imports
PersistentList and PersistentMapping but they are unused; remove the unused
imports (PersistentList and PersistentMapping) from the import statement so only
needed symbols remain, leaving create_session and any other used names untouched
to avoid changing behavior.
🤖 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 `@src/imio/esign/browser/actions.py`:
- Around line 246-249: The code converts session_id with session_id =
int(session_id) without guarding against non-numeric input; wrap the conversion
in a try/except ValueError around the line that casts session_id to int, and on
except call api.portal.show_message(_("Invalid session ID provided!"),
request=self.request, type="error") and return the same redirect as the
empty-case (self.context.absolute_url() + "/@@parapheo"); keep the existing
branch for the falsy session_id unchanged and only add the try/except handling
for the int(...) conversion to avoid raising on bad input.
- Around line 242-285: The __call__ method currently returns URL strings instead
of performing HTTP redirects; replace the three returns that return
self.context.absolute_url() + "/@@parapheo" with actual redirects by calling
self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
(keep existing logic checks around may_recreate_session(), session lookup, and
draft check intact); update the branches after the missing session_id check,
session not found check, and draft-session check to call
self.request.RESPONSE.redirect(...) and then return (or otherwise exit) to match
RemoveFromSessionView._finished() behavior and ensure callers of
create_session/add_files_to_session keep current flow.

---

Nitpick comments:
In `@src/imio/esign/browser/actions.py`:
- Around line 17-18: The file imports PersistentList and PersistentMapping but
they are unused; remove the unused imports (PersistentList and
PersistentMapping) from the import statement so only needed symbols remain,
leaving create_session and any other used names untouched to avoid changing
behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d9b4f04a-6fd6-4a29-8682-76ff51af9a16

📥 Commits

Reviewing files that changed from the base of the PR and between 6232bea and 65a4670.

📒 Files selected for processing (7)
  • CHANGES.rst
  • src/imio/esign/browser/actions.py
  • src/imio/esign/browser/configure.zcml
  • src/imio/esign/browser/table.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py

Comment thread src/imio/esign/browser/actions.py
Comment thread src/imio/esign/browser/actions.py
@chris-adam chris-adam force-pushed the PARAF-420/recreate_session branch from 65a4670 to 6bd272e Compare May 6, 2026 09:58
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/imio/esign/browser/actions.py (1)

240-286: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use self.request.RESPONSE.redirect() instead of returning URL strings.

The __call__ method returns URL strings directly at lines 246, 249, 255, 259, and 286. This renders the URL as HTML response content rather than triggering an actual HTTP redirect. This is inconsistent with RemoveFromSessionView._finished() (line 111) and other similar views in this codebase.

🔧 Proposed fix
     def __call__(self):
         if not self.may_recreate_session():
             raise Unauthorized
         session_id = self.request.form.get("esign_session_id")
         if not session_id:
             api.portal.show_message(_("No session ID provided!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
+            return
         if not session_id.isdigit():
             api.portal.show_message(_("Invalid session ID!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
+            return
         session_id = int(session_id)
         annot = get_session_annotation()
         sessions = annot["sessions"]
         if session_id not in sessions:
             api.portal.show_message(_("Session not found!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
+            return
         old = sessions[session_id]
         if old["state"] == "draft":
             api.portal.show_message(_("Cannot recreate a draft session!"), request=self.request, type="warning")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
+            return
         # ... rest of the method ...
         api.portal.show_message(
             _("New session ${nid} created from session ${oid}", mapping={"nid": new_id, "oid": session_id}),
             request=self.request,
             type="info",
         )
-        return self.context.absolute_url() + "/@@parapheo"
+        self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
🤖 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 `@src/imio/esign/browser/actions.py` around lines 240 - 286, The __call__
method is returning URL strings instead of issuing HTTP redirects; replace each
return of a URL string (all early-error/flow exits and the final success return
in imio.esign.browser.actions.RecreateSessionView.__call__) with a proper
redirect by calling self.request.RESPONSE.redirect(target_url) (use the same
target URLs currently returned, e.g. self.context.absolute_url() +
"/@@parapheo"), then stop further processing by returning None (or an empty
string) after the redirect; ensure every branch that currently returns a URL
uses this pattern so behavior matches RemoveFromSessionView._finished() and
other views.
🤖 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.

Duplicate comments:
In `@src/imio/esign/browser/actions.py`:
- Around line 240-286: The __call__ method is returning URL strings instead of
issuing HTTP redirects; replace each return of a URL string (all
early-error/flow exits and the final success return in
imio.esign.browser.actions.RecreateSessionView.__call__) with a proper redirect
by calling self.request.RESPONSE.redirect(target_url) (use the same target URLs
currently returned, e.g. self.context.absolute_url() + "/@@parapheo"), then stop
further processing by returning None (or an empty string) after the redirect;
ensure every branch that currently returns a URL uses this pattern so behavior
matches RemoveFromSessionView._finished() and other views.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57fe20e9-ce37-4f79-a5e4-350f36116091

📥 Commits

Reviewing files that changed from the base of the PR and between 65a4670 and 6bd272e.

📒 Files selected for processing (7)
  • CHANGES.rst
  • src/imio/esign/browser/actions.py
  • src/imio/esign/browser/configure.zcml
  • src/imio/esign/browser/table.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py

@chris-adam chris-adam force-pushed the PARAF-420/recreate_session branch from 6bd272e to 7dfc7d7 Compare May 6, 2026 12:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@src/imio/esign/browser/actions.py`:
- Around line 263-268: The code passes old.get("title") into create_session
which copies the already-materialized title (with previous session/sign IDs)
into the new draft; instead, regenerate the title from the original title
template or construct a fresh title based on new_id before calling
create_session so the new session UI name and subsequent
create_external_session() sessionName do not retain the old id. Update the call
site that invokes create_session (the new_id, _new_session = create_session(...)
block) to compute a new title (or fallback) rather than reusing old["title"],
referencing create_session, old["title"], new_id and
create_external_session()/sessionName to guide where to change.
- Around line 274-278: Re-adding the same file UIDs into a new session
overwrites annot["uids"][uid] and re-appends the raw UID into annot["c_uids"],
breaking remove/update flows; update the annotation model and the add/remove
functions to be session-aware: change annot["uids"] to map each uid to a
set/list of session IDs (or store per-session metadata) and modify
add_files_to_session so it appends the new session id to annot["uids"][uid]
instead of overwriting and avoids duplicating entries in annot["c_uids"]; then
update RemoveItemFromSessionView, remove_files_from_session, and
remove_context_from_session to consult the session-aware mapping and only remove
entries for the targeted session (and clean up uid entries when no sessions
remain).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f034ce5a-2afd-4efb-bf92-1021d007d922

📥 Commits

Reviewing files that changed from the base of the PR and between 6bd272e and 7dfc7d7.

📒 Files selected for processing (7)
  • CHANGES.rst
  • src/imio/esign/browser/actions.py
  • src/imio/esign/browser/configure.zcml
  • src/imio/esign/browser/table.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/imio/esign/browser/table.py
  • CHANGES.rst

Comment thread src/imio/esign/browser/actions.py
Comment on lines +274 to +278
add_files_to_session(
signers=signers,
files_uids=[f["uid"] for f in old["files"]],
session_id=new_id,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Recreation breaks the UID indexes used by remove/update flows.

This re-adds the same file UIDs into a second session, but add_files_to_session() still overwrites annot["uids"][uid] and appends the raw UID again in annot["c_uids"]. After that, the original session still contains the file while RemoveItemFromSessionView, remove_files_from_session(), and remove_context_from_session() can only target the newest session or leave stale entries behind. The annotation model needs multi-session tracking before this flow is safe.

🤖 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 `@src/imio/esign/browser/actions.py` around lines 274 - 278, Re-adding the same
file UIDs into a new session overwrites annot["uids"][uid] and re-appends the
raw UID into annot["c_uids"], breaking remove/update flows; update the
annotation model and the add/remove functions to be session-aware: change
annot["uids"] to map each uid to a set/list of session IDs (or store per-session
metadata) and modify add_files_to_session so it appends the new session id to
annot["uids"][uid] instead of overwriting and avoids duplicating entries in
annot["c_uids"]; then update RemoveItemFromSessionView,
remove_files_from_session, and remove_context_from_session to consult the
session-aware mapping and only remove entries for the targeted session (and
clean up uid entries when no sessions remain).

@chris-adam chris-adam force-pushed the PARAF-420/recreate_session branch from 7dfc7d7 to 0b8c6cd Compare May 6, 2026 13:11
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/imio/esign/browser/actions.py (2)

240-259: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use self.request.RESPONSE.redirect() instead of returning URL strings.

The __call__ method returns URL strings directly at lines 246, 249, 255, 259, and 285. This renders the URL as plain text in the response body rather than triggering an HTTP redirect. Other views in this file (e.g., RemoveFromSessionView._finished() at line 111) correctly use self.request.RESPONSE.redirect().

🐛 Proposed fix
     def __call__(self):
         if not self.may_recreate_session():
             raise Unauthorized
         session_id = self.request.form.get("esign_session_id")
+        redirect_url = self.context.absolute_url() + "/@@parapheo"
         if not session_id:
             api.portal.show_message(_("No session ID provided!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(redirect_url)
+            return
         if not session_id.isdigit():
             api.portal.show_message(_("Invalid session ID!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(redirect_url)
+            return
         session_id = int(session_id)
         annot = get_session_annotation()
         sessions = annot["sessions"]
         if session_id not in sessions:
             api.portal.show_message(_("Session not found!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(redirect_url)
+            return
         old = sessions[session_id]
         if old["state"] == "draft":
             api.portal.show_message(_("Cannot recreate a draft session!"), request=self.request, type="warning")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(redirect_url)
+            return
         # ... rest of method ...
         api.portal.show_message(
             _("New session ${nid} created from session ${oid}", mapping={"nid": new_id, "oid": session_id}),
             request=self.request,
             type="info",
         )
-        return self.context.absolute_url() + "/@@parapheo"
+        self.request.RESPONSE.redirect(redirect_url)
🤖 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 `@src/imio/esign/browser/actions.py` around lines 240 - 259, In the __call__
method of the view, replace the plain returns of URL strings (the branches that
currently return self.context.absolute_url() + "/@@parapheo") with calls to
self.request.RESPONSE.redirect(...) so the client is actually redirected; update
each branch that shows messages and then returns (invalid/missing session_id,
session not found, draft session, etc.) to call
self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo") and
then return immediately after the redirect to stop processing (mirror the
pattern used in RemoveFromSessionView._finished()). Ensure you keep the existing
api.portal.show_message(...) calls before redirecting and reference the __call__
function and RemoveFromSessionView._finished for the pattern.

273-277: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

UID index overwrites may cause stale references in the old session.

When add_files_to_session is called for the new session, annot["uids"][uid] is overwritten to point to the new session (utils.py line 138). The old session still references these file UIDs in its files list, but the global UID→session mapping now points only to the new session. This can cause issues when attempting to remove files from the old session via RemoveItemFromSessionView or remove_files_from_session().

The updated get_sessions_for mitigates this for session lookups, but the single-session annot["uids"] mapping remains inconsistent.

#!/bin/bash
# Check how remove_files_from_session uses the uids mapping
rg -n -A10 'def remove_files_from_session' src/imio/esign/utils.py

# Check if there are any other places that rely on annot["uids"] being authoritative
rg -n 'annot\["uids"\]' src/imio/esign/
🤖 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 `@src/imio/esign/browser/actions.py` around lines 273 - 277, The call to
add_files_to_session currently overwrites annot["uids"][uid] with the new
session id, breaking the old session's uid mapping and causing
remove_files_from_session / RemoveItemFromSessionView to see stale references;
change the uid mapping to be multi-valued instead of a single id: in
add_files_to_session (and the utils.py code that writes annot["uids"] around
line 138) store a list or set of session ids per uid (e.g.,
annot["uids"].setdefault(uid, set/list).add/append(new_id)) rather than
assigning new_id, and update any readers (remove_files_from_session,
RemoveItemFromSessionView, and any code that assumes a single session id) to
consult this collection or use get_sessions_for to determine the correct
session(s) before removing files.
🤖 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 `@src/imio/esign/browser/actions.py`:
- Around line 263-271: The create_session call is missing the title so the
recreated session gets a default name; update the create_session invocation (the
new_id, _new_session = create_session(...) call) to pass title=old.get("title")
(or old["title"] if guaranteed) so the new session preserves the original title
and include that same title when constructing create_session_custom_data if
needed to track recreation from session_id.

---

Duplicate comments:
In `@src/imio/esign/browser/actions.py`:
- Around line 240-259: In the __call__ method of the view, replace the plain
returns of URL strings (the branches that currently return
self.context.absolute_url() + "/@@parapheo") with calls to
self.request.RESPONSE.redirect(...) so the client is actually redirected; update
each branch that shows messages and then returns (invalid/missing session_id,
session not found, draft session, etc.) to call
self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo") and
then return immediately after the redirect to stop processing (mirror the
pattern used in RemoveFromSessionView._finished()). Ensure you keep the existing
api.portal.show_message(...) calls before redirecting and reference the __call__
function and RemoveFromSessionView._finished for the pattern.
- Around line 273-277: The call to add_files_to_session currently overwrites
annot["uids"][uid] with the new session id, breaking the old session's uid
mapping and causing remove_files_from_session / RemoveItemFromSessionView to see
stale references; change the uid mapping to be multi-valued instead of a single
id: in add_files_to_session (and the utils.py code that writes annot["uids"]
around line 138) store a list or set of session ids per uid (e.g.,
annot["uids"].setdefault(uid, set/list).add/append(new_id)) rather than
assigning new_id, and update any readers (remove_files_from_session,
RemoveItemFromSessionView, and any code that assumes a single session id) to
consult this collection or use get_sessions_for to determine the correct
session(s) before removing files.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f3a24e0c-6dd7-40e9-a978-d01f30cc0336

📥 Commits

Reviewing files that changed from the base of the PR and between 7dfc7d7 and 0b8c6cd.

📒 Files selected for processing (7)
  • CHANGES.rst
  • src/imio/esign/browser/actions.py
  • src/imio/esign/browser/configure.zcml
  • src/imio/esign/browser/table.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/imio/esign/browser/configure.zcml

Comment thread src/imio/esign/browser/actions.py
@chris-adam chris-adam force-pushed the PARAF-420/recreate_session branch from 0b8c6cd to d941359 Compare May 6, 2026 13:36
@chris-adam chris-adam requested review from gbastien and sgeulette May 6, 2026 13:37
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
src/imio/esign/browser/actions.py (2)

273-277: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

File UID tracking may conflict when files exist in multiple sessions.

Re-adding the same file UIDs to a new session via add_files_to_session overwrites annot["uids"][uid] with the new session ID. This means RemoveItemFromSessionView and similar views will only target the newest session, leaving stale entries in the original session.

If files belonging to multiple sessions is an intended use case, the annotation model may need refactoring to track session IDs per UID.

#!/bin/bash
# Check how annot["uids"] is used in removal flows
rg -n -A5 'annot\["uids"\]' src/imio/esign/
🤖 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 `@src/imio/esign/browser/actions.py` around lines 273 - 277,
add_files_to_session currently writes a single session id into
annot["uids"][uid], which overwrites prior sessions and breaks removal logic
(e.g., RemoveItemFromSessionView) for files shared across sessions; change the
model to store a collection of session ids per uid (e.g., a list or set) and
update add_files_to_session to append/merge session ids instead of replacing
them, then update removal flows (RemoveItemFromSessionView and any readers of
annot["uids"]) to remove only the session id for the given operation and to
treat annot["uids"][uid] as a collection; ensure persistence/serialization
handles the new shape and add defensive checks where annot["uids"] is read.

240-285: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use self.request.RESPONSE.redirect() instead of returning URL strings.

The __call__ method returns URL strings directly (lines 246, 249, 255, 259, 285), which renders the URL as response content rather than triggering an HTTP redirect. This is inconsistent with the _finished() pattern used in RemoveFromSessionView (line 111) and RemoveItemFromSessionView (line 139).

🐛 Proposed fix
     def __call__(self):
         if not self.may_recreate_session():
             raise Unauthorized
         session_id = self.request.form.get("esign_session_id")
         if not session_id:
             api.portal.show_message(_("No session ID provided!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
+            return
         if not session_id.isdigit():
             api.portal.show_message(_("Invalid session ID!"), request=self.request, type="error")
-            return self.context.absolute_url() + "/@@parapheo"
+            self.request.RESPONSE.redirect(self.context.absolute_url() + "/@@parapheo")
+            return
         # ... similar changes for lines 255, 259, 285
🤖 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 `@src/imio/esign/browser/actions.py` around lines 240 - 285, The view's
__call__ currently returns URL strings; change each return of
self.context.absolute_url() + "/@@parapheo" to perform an HTTP redirect via
self.request.RESPONSE.redirect(url) (e.g. url = self.context.absolute_url() +
"/@@parapheo"; self.request.RESPONSE.redirect(url)) and then return immediately
(return None or an empty string) so the response is a proper redirect. Update
every early-exit and the success path in this function (the __call__ method that
calls create_session, add_files_to_session, sets self._new_session_id and calls
audit) to use self.request.RESPONSE.redirect instead of returning the URL
string. Ensure no other callers expect the URL string to be returned.
🧹 Nitpick comments (1)
src/imio/esign/tests/test_actions.py (1)

367-371: 💤 Low value

Consider adding strict=True to zip() for safer iteration.

Using zip() without strict=True could silently truncate if the signer lists have different lengths. While this is unlikely given the test setup, adding strict=True (Python 3.10+) would catch any mismatch explicitly.

♻️ Proposed fix
-        for nu, ou in zip(new_session["signers"], old["signers"]):
+        for nu, ou in zip(new_session["signers"], old["signers"], strict=True):
🤖 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 `@src/imio/esign/tests/test_actions.py` around lines 367 - 371, The test loop
using zip(new_session["signers"], old["signers"]) can silently truncate if the
signer lists differ; update the iteration in the test (the for loop that zips
new_session["signers"] and old["signers"] in
src/imio/esign/tests/test_actions.py) to use zip(..., strict=True) so a length
mismatch raises an error and the assertion checks all entries reliably.
🤖 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.

Duplicate comments:
In `@src/imio/esign/browser/actions.py`:
- Around line 273-277: add_files_to_session currently writes a single session id
into annot["uids"][uid], which overwrites prior sessions and breaks removal
logic (e.g., RemoveItemFromSessionView) for files shared across sessions; change
the model to store a collection of session ids per uid (e.g., a list or set) and
update add_files_to_session to append/merge session ids instead of replacing
them, then update removal flows (RemoveItemFromSessionView and any readers of
annot["uids"]) to remove only the session id for the given operation and to
treat annot["uids"][uid] as a collection; ensure persistence/serialization
handles the new shape and add defensive checks where annot["uids"] is read.
- Around line 240-285: The view's __call__ currently returns URL strings; change
each return of self.context.absolute_url() + "/@@parapheo" to perform an HTTP
redirect via self.request.RESPONSE.redirect(url) (e.g. url =
self.context.absolute_url() + "/@@parapheo";
self.request.RESPONSE.redirect(url)) and then return immediately (return None or
an empty string) so the response is a proper redirect. Update every early-exit
and the success path in this function (the __call__ method that calls
create_session, add_files_to_session, sets self._new_session_id and calls audit)
to use self.request.RESPONSE.redirect instead of returning the URL string.
Ensure no other callers expect the URL string to be returned.

---

Nitpick comments:
In `@src/imio/esign/tests/test_actions.py`:
- Around line 367-371: The test loop using zip(new_session["signers"],
old["signers"]) can silently truncate if the signer lists differ; update the
iteration in the test (the for loop that zips new_session["signers"] and
old["signers"] in src/imio/esign/tests/test_actions.py) to use zip(...,
strict=True) so a length mismatch raises an error and the assertion checks all
entries reliably.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9b98bb0-680d-4f34-8def-20e6ac8b0eeb

📥 Commits

Reviewing files that changed from the base of the PR and between 0b8c6cd and d941359.

📒 Files selected for processing (7)
  • CHANGES.rst
  • src/imio/esign/browser/actions.py
  • src/imio/esign/browser/configure.zcml
  • src/imio/esign/browser/table.py
  • src/imio/esign/tests/test_actions.py
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py
✅ Files skipped from review due to trivial changes (1)
  • CHANGES.rst
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/imio/esign/tests/test_utils.py
  • src/imio/esign/utils.py

@chris-adam chris-adam force-pushed the PARAF-420/recreate_session branch from d941359 to 71fdfc9 Compare May 21, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants