Skip to content

Issue 431 cancel ticket htmx toast - #437

Open
egorvaganov wants to merge 3 commits into
TiBillet:mainfrom
egorvaganov:issue-431-cancel-ticket-htmx-toast
Open

Issue 431 cancel ticket htmx toast#437
egorvaganov wants to merge 3 commits into
TiBillet:mainfrom
egorvaganov:issue-431-cancel-ticket-htmx-toast

Conversation

@egorvaganov

Copy link
Copy Markdown

Fixes #431 — Show the ticket cancellation confirmation immediately, without a page reload

Problem

When a user cancels a single ticket from /my_account/my_reservations/, the
confirmation message only appears after a manual page reload.

Root cause: the HTMX branch of MyAccount.cancel_ticket queued the
confirmation via django.contrib.messages but returned an empty
HttpResponse(""). Since the cancel button uses hx-swap="delete", no page
render happens, so the queued message stays invisible until the next full
page load. The error branch (HttpResponse("", status=400)) had the same
problem.

Solution

Implements the pattern already documented in GUIDELINES.md
("HX-Trigger + django.messages") and recommended in tests/PIEGES.md for this
exact class of problem, following the existing HX-Trigger precedent in the
crowds app:

  • BaseBillet/views.pycancel_ticket: both HTMX branches now drain
    the queued messages with get_messages(request) and ship them in the
    HX-Trigger response header as
    {"toast": {"items": [{"level": "...", "text": "..."}]}}.
    • Success: 200 → htmx performs the hx-swap="delete" (the ticket row
      disappears) and the success toast shows immediately.
    • Error: 400 → htmx does not swap on 4xx (the row correctly stays, since
      the ticket was NOT cancelled) but still processes the HX-Trigger
      header, so the error toast shows immediately.
    • The non-HTMX fallback (HttpResponseClientRedirect) is unchanged.
  • BaseBillet/templates/reunion/partials/toasts.html: new global JS
    listener for the toast event. It replicates the exact server-side
    Bootstrap toast markup of the same partial (same classes, colors,
    aria attributes and {% trans %} labels) and appends the toasts into the
    existing #toastContainer. Since the partial is included by all four base
    templates (reunion/base.html, headless.html, blank_base.html,
    faire_festival/base.html), the listener is available on every page and
    reusable for any future HTMX partial that wants to display messages.
    • XSS-safe: message text is inserted with textContent (never
      innerHTML), labels go through |escapejs.
    • Header-injection-safe: json.dumps (default ensure_ascii=True)
      produces pure ASCII, so no CR/LF can reach the header value.
    • Guarded against double registration (window.__tibilletToastListener).
    • No new translatable strings: the listener reuses the {% trans %} labels
      already present in the partial — no .po changes needed.

Tests

New file tests/pytest/test_cancel_ticket_htmx_toast.py (3 tests, all green):

  1. HTMX success: 200, HX-Trigger header carries the success toast
    payload, ticket is CANCELED in DB, cancellation email task queued once.
  2. HTMX error (already-scanned ticket): 400, HX-Trigger carries the
    error toast, ticket stays SCANNED, no email task.
  3. Non-HTMX: legacy behavior preserved — 200 + HX-Redirect to
    /my_account/my_reservations/, no HX-Trigger.
docker exec lespass_django poetry run pytest tests/pytest/test_cancel_ticket_htmx_toast.py -v
# 3 passed
docker exec lespass_django poetry run python manage.py check
# System check identified no issues

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.

Quand on annule un billet, le message de confirmation s'affiche après un reload et pas directement

1 participant