add more status updates
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"""Outgoing email -- plan.md §7 (state-transition side effects).
|
||||
|
||||
Three public functions, one per dedicated template:
|
||||
Five public functions, one per dedicated template:
|
||||
|
||||
send_confirmation_email(submission)
|
||||
Guest path: token-link emailed immediately after `SubmitView` creates
|
||||
@@ -20,7 +20,19 @@ Three public functions, one per dedicated template:
|
||||
rejection reason in the email as on the public detail page.
|
||||
Template: `emails/rejected.*`.
|
||||
|
||||
All three delegate to Django's email machinery. The backend is wired in
|
||||
send_printing_email(submission)
|
||||
Fired on any transition into `status = printing` (operator clicks
|
||||
"Start printing" in admin). Excited tone: "your print is on the
|
||||
bed right now". Template: `emails/printing.*`.
|
||||
|
||||
send_completed_email(submission)
|
||||
Fired on any transition into `status = completed` (operator clicks
|
||||
"Mark completed"). Pickup-ready announcement; renders
|
||||
`submission.operator_notes` as a "note from the operator" callout
|
||||
when present (typically pickup instructions). Template:
|
||||
`emails/completed.*`.
|
||||
|
||||
All five delegate to Django's email machinery. The backend is wired in
|
||||
`hamprint/settings/base.py`: Mailtrap via `django-anymail` when
|
||||
`MAILTRAP_API_TOKEN` is present, console when not. Send failures are caught
|
||||
+ logged so a flaky transport never blocks the submission flow.
|
||||
@@ -146,3 +158,20 @@ def send_verifying_email(sub: Submission) -> bool:
|
||||
will be the queued / printing one."""
|
||||
detail_url = f"{settings.SITE_URL}/p/{sub.slug}/"
|
||||
return _send("verifying", sub, {"detail_url": detail_url})
|
||||
|
||||
|
||||
def send_printing_email(sub: Submission) -> bool:
|
||||
"""Notify the submitter that the print has just started (plan.md §7.3
|
||||
`queued -> printing` transition). Excited tone -- the operator just
|
||||
clicked "Start printing" in admin and the first layer is going down."""
|
||||
detail_url = f"{settings.SITE_URL}/p/{sub.slug}/"
|
||||
return _send("printing", sub, {"detail_url": detail_url})
|
||||
|
||||
|
||||
def send_completed_email(sub: Submission) -> bool:
|
||||
"""Notify the submitter that the print finished successfully and is
|
||||
ready for pickup (plan.md §7.3 `printing -> completed` transition).
|
||||
`submission.operator_notes` is rendered when present so any
|
||||
pickup-instruction the operator typed in admin reaches the user."""
|
||||
detail_url = f"{settings.SITE_URL}/p/{sub.slug}/"
|
||||
return _send("completed", sub, {"detail_url": detail_url})
|
||||
|
||||
@@ -294,11 +294,12 @@ class Submission(models.Model):
|
||||
currently owns the row, so the per-email cap and trust list don't
|
||||
depend on the caller remembering to set it.
|
||||
|
||||
Additionally: when an UPDATE flips `status` to `rejected` from any
|
||||
other state, this method queues `send_rejection_email()` via
|
||||
`transaction.on_commit`. Centralising the email here means **every**
|
||||
save path -- admin, the validation worker, ad-hoc shell, any future
|
||||
view -- fires the email through a single hook. Plan.md §7.3.
|
||||
Additionally: when an UPDATE flips `status` to a state with a
|
||||
dedicated email (`rejected`, `printing`, `completed`), this method
|
||||
queues the matching `send_*_email()` via `transaction.on_commit`.
|
||||
Centralising the dispatch here means **every** save path -- admin,
|
||||
the validation worker, ad-hoc shell, any future view -- fires the
|
||||
email through a single hook. Plan.md §7.3.
|
||||
"""
|
||||
if not self.slug:
|
||||
self.slug = self._generate_unique_slug()
|
||||
@@ -319,26 +320,36 @@ class Submission(models.Model):
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Fire on TRANSITIONS only: an UPDATE that flips status to rejected.
|
||||
# Don't fire on inserts that start out as rejected -- those should
|
||||
# be impossible by design (plan.md §7.3 doesn't define a (none) ->
|
||||
# rejected edge), and even if some weird path creates one we'd
|
||||
# rather stay silent than spam a fresh victim.
|
||||
if (
|
||||
not is_new
|
||||
and old_status != new_status
|
||||
and new_status == self.Status.REJECTED
|
||||
):
|
||||
# Fire on TRANSITIONS only: an UPDATE that flips status into one of
|
||||
# the email-bearing target states. Don't fire on inserts that start
|
||||
# out in those states -- by plan.md §7.3 no submit-time edge lands
|
||||
# in rejected/printing/completed, and even if some weird path did,
|
||||
# we'd rather stay silent than send "your print is ready" to a fresh
|
||||
# victim of a fixture/data-migration import.
|
||||
if not is_new and old_status != new_status:
|
||||
# Local imports keep this module out of the apps/submissions
|
||||
# import-cycle (emails.py imports from here).
|
||||
from django.db import transaction
|
||||
from .emails import send_rejection_email
|
||||
from .emails import (
|
||||
send_completed_email,
|
||||
send_printing_email,
|
||||
send_rejection_email,
|
||||
)
|
||||
|
||||
if new_status == self.Status.REJECTED:
|
||||
transaction.on_commit(
|
||||
lambda sub=self, prev=old_status: send_rejection_email(
|
||||
sub, previous_status=prev
|
||||
)
|
||||
)
|
||||
elif new_status == self.Status.PRINTING:
|
||||
transaction.on_commit(
|
||||
lambda sub=self: send_printing_email(sub)
|
||||
)
|
||||
elif new_status == self.Status.COMPLETED:
|
||||
transaction.on_commit(
|
||||
lambda sub=self: send_completed_email(sub)
|
||||
)
|
||||
|
||||
# Refresh the snapshot so a follow-up save on the same instance
|
||||
# compares against the just-persisted state, not the original load.
|
||||
|
||||
51
templates/emails/completed.body.html
Normal file
51
templates/emails/completed.body.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends "emails/_base.html" %}
|
||||
{% block body %}
|
||||
<h1 style="margin:0 0 12px 0; font-size:22px; font-weight:700; color:#065f46; letter-spacing:-0.01em;">Your print is ready</h1>
|
||||
|
||||
<p style="margin:0 0 16px 0; color:#334155; font-size:15px; line-height:1.55;">
|
||||
Done! <strong style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; color:#92400e; font-weight:700;">{{ submission.slug }}</strong> came off the printer successfully and is waiting for you at hamlab.lt.
|
||||
</p>
|
||||
|
||||
{# Pill colour comes from `Submission.STATUS_EMAIL_COLORS[COMPLETED]` -- the emerald palette used everywhere else for the "success" terminal state. #}
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 20px 0;">
|
||||
<tr>
|
||||
<td style="padding:6px 14px; background-color:{{ submission.status_email_style.bg }}; border-radius:9999px;">
|
||||
<span style="color:{{ submission.status_email_style.fg }}; font-weight:600; font-size:14px; letter-spacing:0.01em;">
|
||||
{{ submission.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if submission.operator_notes %}
|
||||
{# Pickup-instructions callout -- only rendered when the operator left a note (e.g. "in the green bin by the lasers"). #}
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%" style="margin:0 0 24px 0;">
|
||||
<tr>
|
||||
<td style="padding:16px; background-color:#ecfdf5; border-left:3px solid #10b981; border-radius:0 4px 4px 0;">
|
||||
<p style="margin:0 0 6px 0; font-size:11px; font-weight:600; color:#065f46; text-transform:uppercase; letter-spacing:0.06em;">
|
||||
Note from the operator
|
||||
</p>
|
||||
<p style="margin:0; color:#334155; font-size:14px; line-height:1.55; white-space:pre-line;">{{ submission.operator_notes }}</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<p style="margin:0 0 24px 0; color:#334155; font-size:15px; line-height:1.55;">
|
||||
Come grab it whenever the lab is open. Thanks for printing with us!
|
||||
</p>
|
||||
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="background-color:#10b981; border-radius:6px;">
|
||||
<a href="{{ detail_url }}" style="display:inline-block; padding:12px 24px; color:#ffffff; font-weight:600; font-size:15px; text-decoration:none; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
||||
View pickup details
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin:18px 0 0 0; font-size:13px; color:#64748b;">
|
||||
Direct link: <span style="color:#475569; word-break:break-all; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">{{ detail_url }}</span>
|
||||
</p>
|
||||
{% endblock %}
|
||||
19
templates/emails/completed.body.txt
Normal file
19
templates/emails/completed.body.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Hi,
|
||||
|
||||
Your hamprint is done! "{{ submission.slug }}" came off the printer
|
||||
successfully and is waiting for you at hamlab.lt.
|
||||
|
||||
Codename : {{ submission.slug }}
|
||||
Status : Completed
|
||||
{% if submission.operator_notes %}
|
||||
A note from the operator:
|
||||
|
||||
{{ submission.operator_notes }}
|
||||
{% endif %}
|
||||
Come grab it whenever the lab is open. Thanks for printing with us!
|
||||
|
||||
Pickup details and a photo (if the operator left one) are here:
|
||||
{{ detail_url }}
|
||||
|
||||
— hamprint
|
||||
{{ site_url }}
|
||||
1
templates/emails/completed.subject.txt
Normal file
1
templates/emails/completed.subject.txt
Normal file
@@ -0,0 +1 @@
|
||||
hamprint: {{ submission.slug }} is ready for pickup
|
||||
37
templates/emails/printing.body.html
Normal file
37
templates/emails/printing.body.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "emails/_base.html" %}
|
||||
{% block body %}
|
||||
<h1 style="margin:0 0 12px 0; font-size:22px; font-weight:700; color:#9a3412; letter-spacing:-0.01em;">Your print is starting</h1>
|
||||
|
||||
<p style="margin:0 0 16px 0; color:#334155; font-size:15px; line-height:1.55;">
|
||||
Great news — <strong style="font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; color:#92400e; font-weight:700;">{{ submission.slug }}</strong> is on the printer right now. The operator has started the job and the first layer is going down.
|
||||
</p>
|
||||
|
||||
{# Pill colour comes from `Submission.STATUS_EMAIL_COLORS[PRINTING]` -- warm orange to mirror the live-printing chip on the dashboard. #}
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0 0 20px 0;">
|
||||
<tr>
|
||||
<td style="padding:6px 14px; background-color:{{ submission.status_email_style.bg }}; border-radius:9999px;">
|
||||
<span style="color:{{ submission.status_email_style.fg }}; font-weight:600; font-size:14px; letter-spacing:0.01em;">
|
||||
{{ submission.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin:0 0 24px 0; color:#334155; font-size:15px; line-height:1.55;">
|
||||
We'll email you again the moment it finishes so you know when to come pick it up. Nothing for you to do right now — feel free to track progress at the link below.
|
||||
</p>
|
||||
|
||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="background-color:#f59e0b; border-radius:6px;">
|
||||
<a href="{{ detail_url }}" style="display:inline-block; padding:12px 24px; color:#ffffff; font-weight:600; font-size:15px; text-decoration:none; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
||||
Follow the print
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p style="margin:18px 0 0 0; font-size:13px; color:#64748b;">
|
||||
Direct link: <span style="color:#475569; word-break:break-all; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;">{{ detail_url }}</span>
|
||||
</p>
|
||||
{% endblock %}
|
||||
17
templates/emails/printing.body.txt
Normal file
17
templates/emails/printing.body.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
Hi,
|
||||
|
||||
Great news -- your hamprint submission "{{ submission.slug }}" is on the
|
||||
printer right now. The operator has started the job and the first layer
|
||||
is going down.
|
||||
|
||||
Codename : {{ submission.slug }}
|
||||
Status : Printing
|
||||
|
||||
We'll send one more email when it finishes, so you know when to come
|
||||
pick it up. No action needed in the meantime.
|
||||
|
||||
You can also follow along here:
|
||||
{{ detail_url }}
|
||||
|
||||
— hamprint
|
||||
{{ site_url }}
|
||||
1
templates/emails/printing.subject.txt
Normal file
1
templates/emails/printing.subject.txt
Normal file
@@ -0,0 +1 @@
|
||||
hamprint: {{ submission.slug }} is on the printer
|
||||
Reference in New Issue
Block a user