supporters: Simplify and extend docs

This commit is contained in:
Ben Sturmfels 2024-10-25 11:30:22 +11:00
parent 4cdfbdd722
commit d82122daa4
Signed by: bsturmfels
GPG key ID: 023C05E2C9C068F0
2 changed files with 26 additions and 15 deletions

View file

@ -1,6 +1,4 @@
from django import forms from django import forms
from django.utils.safestring import mark_safe
from django.urls import reverse
from .models import SustainerOrder from .models import SustainerOrder
@ -10,7 +8,7 @@ class SustainerFormRenderer(forms.renderers.DjangoTemplates):
class ButtonRadioSelect(forms.widgets.RadioSelect): class ButtonRadioSelect(forms.widgets.RadioSelect):
"""Radio button styled like a button. BYO CSS.""" """Radio button styled like a button."""
# Extra <span> wrappers to support CSS # Extra <span> wrappers to support CSS
option_template_name = 'supporters/buttonradio_option.html' option_template_name = 'supporters/buttonradio_option.html'
@ -27,7 +25,25 @@ class ButtonRadioSelect(forms.widgets.RadioSelect):
class SustainerForm(forms.ModelForm): class SustainerForm(forms.ModelForm):
# Used to pre-fill the price selector """Sustainer sign-up
The logic for this form is somewhat spread between this Django form and the Django
template and Alpine JS code in the template.
Having to define some of the the Alpine JS attributes here in the form and some in
the template feels awkward, and I wish there was a better way. Django Crispy Forms
is typically a good option, but I really wanted to see if the new Django 5 form
improvements could beat that (eg. ".as_field_group"). They certainly help, but put
several levels of abstraction between you and the HTML (eg. renderers) and spread
your HTML across various template and code files. While I appreciate not having to
write code to render checked and unchecked boxes, designing attractive interactive
forms shouldn't be this complicated.
Alpine JS has its own trade-offs here. There's nearly no JavaScript as such, but the
"x-.." attributes are meaningless until you read the Alpine docs.
"""
# To pre-fill the price option buttons in the case of server-side validation errors.
amount_option = forms.CharField(required=False) amount_option = forms.CharField(required=False)
template_name = 'supporters/sustainer_form.html' template_name = 'supporters/sustainer_form.html'
@ -62,7 +78,7 @@ class SustainerForm(forms.ModelForm):
} }
), ),
'amount': forms.widgets.NumberInput( 'amount': forms.widgets.NumberInput(
# Keeping default widget, just neater to add many attrs here. # Retaining default widget, just neater to add many attrs here.
attrs={ attrs={
# So we can update the amount field from the amount_option selected. # So we can update the amount field from the amount_option selected.
'x-model': 'amount', 'x-model': 'amount',
@ -87,20 +103,15 @@ class SustainerForm(forms.ModelForm):
recurring = self.cleaned_data.get('recurring', '') recurring = self.cleaned_data.get('recurring', '')
amount = self.cleaned_data.get('amount', 0) amount = self.cleaned_data.get('amount', 0)
minimum = self.MONTH_MINIMUM if recurring == 'month' else self.YEAR_MINIMUM minimum = self.MONTH_MINIMUM if recurring == 'month' else self.YEAR_MINIMUM
donate_url = reverse('donate')
if amount < minimum: if amount < minimum:
self.add_error( self.add_error('', f'${minimum:d} is a minimum for Conservancy Sustainers.')
'',
mark_safe(
f'${minimum:d} is a minimum for Conservancy Sustainers. <a href="{donate_url}">Donate smaller amounts here</a>.'
),
)
tshirt_size = self.cleaned_data.get('tshirt_size') tshirt_size = self.cleaned_data.get('tshirt_size')
if tshirt_size and not all( address_provided = all(
[ [
self.cleaned_data.get('street'), self.cleaned_data.get('street'),
self.cleaned_data.get('city'), self.cleaned_data.get('city'),
self.cleaned_data.get('country'), self.cleaned_data.get('country'),
] ]
): )
if tshirt_size and not address_provided:
self.add_error('street', 'No address provided') self.add_error('street', 'No address provided')

View file

@ -50,7 +50,7 @@
{# Alpine JS is used to show different payments amounts for monthly/annual, write the selected payment amount into the "amount" field, reset the seleted amount when you change monthly/annual and pop out the address when you select a T-shirt. #} {# Alpine JS is used to show different payments amounts for monthly/annual, write the selected payment amount into the "amount" field, reset the seleted amount when you change monthly/annual and pop out the address when you select a T-shirt. #}
<form method="post" action="." <form method="post" action="."
{# Pre-fill field defaults in case of server-side validation error. Otherwise Alpine JS will override them. #} {# Pre-fill field defaults in case of server-side validation error. Otherwise Alpine JS will override them. Could alternatively use the `json_script` tag here. #}
x-data="{ x-data="{
recurring: '{{ form.recurring.value|escapejs }}', recurring: '{{ form.recurring.value|escapejs }}',
amount: parseInt('{{ form.amount.value|escapejs }}'), amount: parseInt('{{ form.amount.value|escapejs }}'),