contacts: Update unsubscribe to work through browser GET interface

Also add field to mark unsubscribes as actioned.
This commit is contained in:
Ben Sturmfels 2024-12-11 10:37:02 +11:00
parent c377b329e2
commit de861fe14d
Signed by: bsturmfels
GPG key ID: 023C05E2C9C068F0
4 changed files with 40 additions and 2 deletions

View file

@ -6,4 +6,5 @@ from .models import Unsubscription
@admin.register(Unsubscription) @admin.register(Unsubscription)
class UnsubscriptionAdmin(admin.ModelAdmin): class UnsubscriptionAdmin(admin.ModelAdmin):
list_display = ['created', 'email', 'mailout'] list_display = ['created', 'email', 'mailout']
list_filter = ['mailout', 'actioned']
search_fields = ['email', 'mailout'] search_fields = ['email', 'mailout']

View file

@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-12-10 18:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contacts', '0002_unsubscription_mailout'),
]
operations = [
migrations.AddField(
model_name='unsubscription',
name='actioned',
field=models.DateField(blank=True, null=True),
),
]

View file

@ -5,6 +5,13 @@ class Unsubscription(models.Model):
created = models.DateTimeField(auto_now_add=True, blank=True) created = models.DateTimeField(auto_now_add=True, blank=True)
email = models.EmailField() email = models.EmailField()
mailout = models.SlugField() mailout = models.SlugField()
actioned = models.DateField(null=True, blank=True)
class Meta: class Meta:
ordering = ['created'] ordering = ['created']
def __str__(self):
if self.mailout:
return f'{self.email} ({self.mailout})'
else:
return self.email

View file

@ -27,15 +27,27 @@ def unsubscribe(request):
Interfaces like Gmail will then provide a user interface to unsubscribe Interfaces like Gmail will then provide a user interface to unsubscribe
which will hit this endpoint. which will hit this endpoint.
Our understanding is that Gmail will submit the form with and HTTP POST request, but
with GET-style URL parameters. Hence this feature has been built to work both with
GET and POST-style data as well as GET and POST methods, just in case. Test it like
this:
curl -X POST -d 'email=foo@bar.com&mailout=jan2024-new' https://sfconservancy.org/contacts/unsubscribe/
curl -X POST 'https://sfconservancy.org/contacts/unsubscribe/?email=foo@bar.com&mailout=jan2024-new'
Or visit in a browser:
https://sfconservancy.org/contacts/unsubscribe/?email=foo@bar.com&mailout=jan2024-new
""" """
if request.method == 'POST': if request.method == 'POST':
logger.debug('Unsubscribe GET: %s', request.GET) logger.debug('Unsubscribe GET: %s', request.GET)
logger.debug('Unsubscribe POST: %s', request.POST) logger.debug('Unsubscribe POST: %s', request.POST)
form = UnsubscribeForm(request.GET) form = UnsubscribeForm(request.GET.dict() | request.POST.dict())
if form.is_valid(): if form.is_valid():
form.save() form.save()
logger.info('Unsubscribed %s', form.cleaned_data['email']) logger.info('Unsubscribed %s', form.cleaned_data['email'])
return render(request, 'contacts/unsubscribe_success.html') return render(request, 'contacts/unsubscribe_success.html')
else: else:
form = UnsubscribeForm() form = UnsubscribeForm(request.GET.dict() | request.POST.dict())
return render(request, 'contacts/unsubscribe.html', {'form': form}) return render(request, 'contacts/unsubscribe.html', {'form': form})