Some checks failed
lint.yml / feat: attach a self-describing ticket card to the email (1.6.1-aio.10) (pull_request) Failing after 0s
The v1.6.1-aio.9 mail still scored 8.4/10 on mail-tester: the remaining
deduction was HTML_IMAGE_ONLY (1.8) — an HTML part whose only content of
note is a remote <img>. Remote images are also blocked by default in most
clients until the reader opts in, and a bare QR saved from that mail says
nothing about what it opens.
- New `qr.py` module (QR + logo helpers moved out of views_api) with
`render_ticket_card`: site title, event name, when/where, the branded
QR, name on ticket, ticket id and the door instruction, laid out with
the bundled DejaVu Sans; `format_event_when` gives "Fri 19 Feb 2027,
16:00 - 20:00"; filenames are `ticket-<event-slug>-<id8>.png`.
- `GET /events/api/v1/ticket-card/{ticket_id}` serves the same PNG
(anonymous, like the QR endpoint); the email's "Ticket image" link
now points there.
- The ticket email becomes multipart/mixed: text + HTML alternatives
(URLs as links, no <img>) plus the card as a PNG attachment, which
clients show inline at the end of the message and which works offline
at the door.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EYwoAkZZmXMMmaBp4WGUBo
64 lines
2 KiB
Python
64 lines
2 KiB
Python
from io import BytesIO
|
|
|
|
from PIL import Image
|
|
|
|
from ..qr import make_qr_png
|
|
|
|
|
|
def test_make_qr_png_renders_requested_size():
|
|
img = make_qr_png("ticket://abc123", size=200)
|
|
assert img.size == (200, 200)
|
|
|
|
|
|
def test_make_qr_png_pastes_a_centred_logo():
|
|
logo = Image.new("RGBA", (64, 64), (255, 0, 0, 255))
|
|
img = make_qr_png("ticket://abc123", size=300, logo=logo)
|
|
assert img.size == (300, 300)
|
|
# The centre pixel is inside the pasted logo, so it is red — a plain
|
|
# QR would only ever have black or white there.
|
|
assert img.getpixel((150, 150))[:3] == (255, 0, 0)
|
|
out = BytesIO()
|
|
img.save(out, format="PNG")
|
|
assert out.getvalue().startswith(b"\x89PNG")
|
|
|
|
|
|
def test_render_ticket_card_is_self_describing():
|
|
from datetime import datetime, timezone
|
|
|
|
from ..models import Event, Ticket
|
|
from ..qr import format_event_when, render_ticket_card, ticket_card_filename
|
|
|
|
now = datetime.now(timezone.utc)
|
|
event = Event(
|
|
id="evt1",
|
|
wallet="w",
|
|
name="Château du Faune | Uru Ecstatic Dance",
|
|
info="",
|
|
closing_date="2027-02-19",
|
|
event_start_date="2027-02-19T16:00:00+01:00",
|
|
event_end_date="2027-02-19T20:00:00+01:00",
|
|
location="The Chateau",
|
|
amount_tickets=10,
|
|
price_per_ticket=15,
|
|
time=now,
|
|
)
|
|
ticket = Ticket(
|
|
id="8auNuuaBv7TFGj7BYoVnTN",
|
|
wallet="w",
|
|
event="evt1",
|
|
name="Guest Test",
|
|
email="g@example.com",
|
|
registered=False,
|
|
paid=True,
|
|
time=now,
|
|
reg_timestamp=now,
|
|
)
|
|
assert format_event_when(event) == "Fri 19 Feb 2027, 16:00 - 20:00"
|
|
assert (
|
|
ticket_card_filename(ticket, event)
|
|
== "ticket-ch-teau-du-faune-uru-ecstatic-dance-8auNuuaB.png"
|
|
)
|
|
card = render_ticket_card(ticket, event, site_title="Oyez!")
|
|
assert card.width == 800 and card.height > 800
|
|
# QR area is centred; its finder pattern is black
|
|
assert card.getpixel((400, card.height // 2)) in ((0, 0, 0), (255, 255, 255))
|