Skip to content

Commit a715613

Browse files
committed
feat: no-js
1 parent 71f93c1 commit a715613

7 files changed

Lines changed: 127 additions & 53 deletions

File tree

mailing_list/mixins.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ class MailingListCardMixin:
5050
Adds the variables needed by v3/includes/_mailing_list_card.html:
5151
mailing_list_card_subscribe_url
5252
mailing_list_card_list_id
53-
mailing_list_card_state ("pending", "active", or None)
54-
mailing_list_card_user_email (authenticated users only)
53+
mailing_list_card_state ("pending", "active", "error", or None)
54+
mailing_list_card_error_message (set on error state, used by no-JS PRG flow)
55+
mailing_list_card_user_email (authenticated users only, or from PRG params)
5556
mailing_list_card_manage_url (authenticated users only)
5657
mailing_list_card_subscription_count (authenticated users only — ACTIVE count only)
5758
"""
@@ -76,4 +77,16 @@ def get_context_data(self, **kwargs):
7677
reverse("profile-account")
7778
)
7879

80+
# URL-param overrides for the no-JS PRG flow.
81+
# Error state always wins (DB record was rolled back on failure).
82+
# Anonymous pending has no DB record so the URL param is the only source.
83+
ml_state_param = request.GET.get("ml_state")
84+
if ml_state_param == "error":
85+
context["mailing_list_card_state"] = "error"
86+
context["mailing_list_card_error_message"] = request.GET.get("ml_error", "")
87+
context["mailing_list_card_user_email"] = request.GET.get("ml_email", "")
88+
elif ml_state_param == "pending" and not request.user.is_authenticated:
89+
context["mailing_list_card_state"] = "pending"
90+
context["mailing_list_card_user_email"] = request.GET.get("ml_email", "")
91+
7992
return context

mailing_list/views.py

Lines changed: 106 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import datetime
22
import logging
3+
from urllib.parse import urlencode, urlparse
34

45
from django.conf import settings
56
from django.contrib.auth import get_user_model
67
from django.contrib.auth.mixins import LoginRequiredMixin
78
from django.core import signing
89
from django.core.mail import send_mail
10+
from django.http import HttpResponseRedirect
911
from django.shortcuts import render
1012
from django.template.loader import render_to_string
1113
from django.urls import reverse
@@ -27,6 +29,21 @@
2729
_CONFIRM_MAX_AGE = 7 * 24 * 60 * 60 # 7 days in seconds
2830

2931

32+
def _is_htmx(request) -> bool:
33+
return request.headers.get("HX-Request") == "true"
34+
35+
36+
def _prg_redirect(request, **params) -> HttpResponseRedirect:
37+
"""Redirect back to the referring page, with optional query params for card state.
38+
39+
Strips the referer to path-only to prevent open-redirect issues.
40+
"""
41+
referer = request.headers.get("referer", "/")
42+
path = urlparse(referer).path or "/"
43+
qs = urlencode({k: v for k, v in params.items() if v})
44+
return HttpResponseRedirect(f"{path}?{qs}" if qs else path)
45+
46+
3047
def _format_duration(seconds: int) -> str:
3148
now = timezone.now()
3249
return timesince(now - datetime.timedelta(seconds=seconds), now, depth=1)
@@ -184,20 +201,31 @@ def post(self, request):
184201
list_id = request.POST.get("list_id", "").strip()
185202

186203
if not email:
187-
return self._card(
188-
request,
189-
state="error",
190-
error_message="Email is required.",
191-
list_id=list_id or "boost.lists.boost.org",
204+
if _is_htmx(request):
205+
return self._card(
206+
request,
207+
state="error",
208+
error_message="Email is required.",
209+
list_id=list_id or "boost.lists.boost.org",
210+
)
211+
return _prg_redirect(
212+
request, ml_state="error", ml_error="Email is required."
192213
)
193214

194215
if list_id not in managed_lists:
195-
return self._card(
216+
if _is_htmx(request):
217+
return self._card(
218+
request,
219+
state="error",
220+
error_message="Invalid mailing list.",
221+
user_email=email,
222+
list_id=list_id or "boost.lists.boost.org",
223+
)
224+
return _prg_redirect(
196225
request,
197-
state="error",
198-
error_message="Invalid mailing list.",
199-
user_email=email,
200-
list_id=list_id or "boost.lists.boost.org",
226+
ml_state="error",
227+
ml_error="Invalid mailing list.",
228+
ml_email=email,
201229
)
202230

203231
if request.user.is_authenticated:
@@ -213,25 +241,29 @@ def _handle_authenticated(self, request, email, list_id, managed_lists):
213241

214242
if existing:
215243
if existing.status == SubscriptionStatus.PENDING:
216-
return self._card(
217-
request,
218-
state="pending",
219-
user_email=existing.email,
220-
list_id=list_id,
221-
manage_url=manage_url,
222-
)
244+
if _is_htmx(request):
245+
return self._card(
246+
request,
247+
state="pending",
248+
user_email=existing.email,
249+
list_id=list_id,
250+
manage_url=manage_url,
251+
)
252+
return _prg_redirect(request)
223253
subscription_count = UserMailingListSubscription.objects.filter(
224254
user=request.user, list_id__in=managed_lists
225255
).count()
226-
return render(
227-
request,
228-
"v3/mailing_list/_subscribe_success_card.html",
229-
{
230-
"email": existing.email,
231-
"subscription_count": subscription_count,
232-
"manage_url": manage_url,
233-
},
234-
)
256+
if _is_htmx(request):
257+
return render(
258+
request,
259+
"v3/mailing_list/_subscribe_success_card.html",
260+
{
261+
"email": existing.email,
262+
"subscription_count": subscription_count,
263+
"manage_url": manage_url,
264+
},
265+
)
266+
return _prg_redirect(request)
235267

236268
UserMailingListSubscription.objects.create(
237269
user=request.user,
@@ -246,31 +278,47 @@ def _handle_authenticated(self, request, email, list_id, managed_lists):
246278
UserMailingListSubscription.objects.filter(
247279
user=request.user, list_id=list_id
248280
).delete()
281+
if _is_htmx(request):
282+
return self._card(
283+
request,
284+
state="error",
285+
error_message="Could not send confirmation email. Please try again.",
286+
user_email=email,
287+
list_id=list_id,
288+
)
289+
return _prg_redirect(
290+
request,
291+
ml_state="error",
292+
ml_error="Could not send confirmation email. Please try again.",
293+
ml_email=email,
294+
)
295+
296+
if _is_htmx(request):
249297
return self._card(
250298
request,
251-
state="error",
252-
error_message="Could not send confirmation email. Please try again.",
299+
state="pending",
253300
user_email=email,
254301
list_id=list_id,
302+
manage_url=manage_url,
255303
)
256-
257-
return self._card(
258-
request,
259-
state="pending",
260-
user_email=email,
261-
list_id=list_id,
262-
manage_url=manage_url,
263-
)
304+
return _prg_redirect(request)
264305

265306
def _handle_anonymous(self, request, email, list_id):
266307
try:
267308
if mailman_is_confirmed(email, list_id):
268-
return self._card(
309+
if _is_htmx(request):
310+
return self._card(
311+
request,
312+
state="error",
313+
error_message=f"{email} is already subscribed to this list.",
314+
user_email=email,
315+
list_id=list_id,
316+
)
317+
return _prg_redirect(
269318
request,
270-
state="error",
271-
error_message=f"{email} is already subscribed to this list.",
272-
user_email=email,
273-
list_id=list_id,
319+
ml_state="error",
320+
ml_error=f"{email} is already subscribed to this list.",
321+
ml_email=email,
274322
)
275323
except MailmanAPIError:
276324
pass # can't determine — proceed and let Mailman handle it on confirm
@@ -279,15 +327,26 @@ def _handle_anonymous(self, request, email, list_id):
279327
_send_confirmation_email(request, email, None, [list_id])
280328
except Exception as exc:
281329
logger.error("Failed to send confirmation email to %s: %s", email, exc)
282-
return self._card(
330+
if _is_htmx(request):
331+
return self._card(
332+
request,
333+
state="error",
334+
error_message="Could not send confirmation email. Please try again.",
335+
user_email=email,
336+
list_id=list_id,
337+
)
338+
return _prg_redirect(
283339
request,
284-
state="error",
285-
error_message="Could not send confirmation email. Please try again.",
286-
user_email=email,
287-
list_id=list_id,
340+
ml_state="error",
341+
ml_error="Could not send confirmation email. Please try again.",
342+
ml_email=email,
288343
)
289344

290-
return self._card(request, state="pending", user_email=email, list_id=list_id)
345+
if _is_htmx(request):
346+
return self._card(
347+
request, state="pending", user_email=email, list_id=list_id
348+
)
349+
return _prg_redirect(request, ml_state="pending", ml_email=email)
291350

292351

293352
class ConfirmSubscriptionView(View):

templates/v3/community.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
{% include "v3/includes/_post_card.html" with heading="Posts from the Boost community" items=posts variant="card" theme="teal" primary_cta_label="View all posts" primary_cta_url=news_url %}
3939
</div>
4040
<div class="community-masonry__cell community-masonry__cell--mailing">
41-
{% include "v3/includes/_mailing_list_card.html" with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count %}
41+
{% include "v3/includes/_mailing_list_card.html" with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count error_message=mailing_list_card_error_message %}
4242
</div>
4343
</div>
4444
</div>

templates/v3/includes/_mailing_list_card.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
</div>
3939
<hr class="card__hr" />
4040
<form
41+
action="{{ subscribe_url }}"
42+
method="post"
4143
class="mailing-list-card__form"
4244
hx-post="{{ subscribe_url }}"
4345
hx-target="closest .basic-card"

templates/v3/learn_page.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ <h1>Start anywhere. Learn everything. Build anything.</h1>
4949
{% endwith %}
5050
</div>
5151
<div class="item-e">
52-
{% include 'v3/includes/_mailing_list_card.html' with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count %}
52+
{% include 'v3/includes/_mailing_list_card.html' with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count error_message=mailing_list_card_error_message %}
5353
</div>
5454
</div>
5555
</div>

templates/v3/libraries/library-subpage.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
{% include "v3/includes/_post_card.html" with heading="Latest "|add:object.display_name|add:" posts" items=library_posts variant="card" theme="teal" layout="horizontal" primary_cta_label="View all posts" primary_cta_url="/news/" %}
5454
</div>
5555
<div class="item-b">
56-
{% include "v3/includes/_mailing_list_card.html" with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count %}
56+
{% include "v3/includes/_mailing_list_card.html" with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count error_message=mailing_list_card_error_message %}
5757
</div>
5858
</div>
5959

templates/v3/release_detail.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
{% endif %}
3535

3636
<section class="release-detail__bottom-cards" aria-label="Community and resources">
37-
{% include "v3/includes/_mailing_list_card.html" with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count %}
37+
{% include "v3/includes/_mailing_list_card.html" with subscribe_url=mailing_list_card_subscribe_url list_id=mailing_list_card_list_id state=mailing_list_card_state user_email=mailing_list_card_user_email manage_url=mailing_list_card_manage_url subscription_count=mailing_list_card_subscription_count error_message=mailing_list_card_error_message %}
3838

3939
{% include "v3/includes/_basic_card.html" with title="Want to fix bugs, review code, maintain libraries, and propose new features?" text="Read our contributors guide and join the Boost community." primary_button_url="/contribute" primary_button_label="Start here" primary_button_style="green" theme="green" only %}
4040
{% large_static 'img/v3/home-page/heros.png' as image_url %}

0 commit comments

Comments
 (0)