diff --git a/conservancy/contacts/admin.py b/conservancy/contacts/admin.py index 9a61da2b..e5faaba2 100644 --- a/conservancy/contacts/admin.py +++ b/conservancy/contacts/admin.py @@ -6,4 +6,5 @@ from .models import Unsubscription @admin.register(Unsubscription) class UnsubscriptionAdmin(admin.ModelAdmin): list_display = ['created', 'email', 'mailout'] + list_filter = ['mailout', 'actioned'] search_fields = ['email', 'mailout'] diff --git a/conservancy/contacts/migrations/0003_unsubscription_actioned.py b/conservancy/contacts/migrations/0003_unsubscription_actioned.py new file mode 100644 index 00000000..5ca7b62b --- /dev/null +++ b/conservancy/contacts/migrations/0003_unsubscription_actioned.py @@ -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), + ), + ] diff --git a/conservancy/contacts/models.py b/conservancy/contacts/models.py index 729b1c5c..dc78c668 100644 --- a/conservancy/contacts/models.py +++ b/conservancy/contacts/models.py @@ -5,6 +5,13 @@ class Unsubscription(models.Model): created = models.DateTimeField(auto_now_add=True, blank=True) email = models.EmailField() mailout = models.SlugField() + actioned = models.DateField(null=True, blank=True) class Meta: ordering = ['created'] + + def __str__(self): + if self.mailout: + return f'{self.email} ({self.mailout})' + else: + return self.email diff --git a/conservancy/contacts/views.py b/conservancy/contacts/views.py index ed528120..78b05b90 100644 --- a/conservancy/contacts/views.py +++ b/conservancy/contacts/views.py @@ -27,15 +27,27 @@ def unsubscribe(request): Interfaces like Gmail will then provide a user interface to unsubscribe 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': logger.debug('Unsubscribe GET: %s', request.GET) logger.debug('Unsubscribe POST: %s', request.POST) - form = UnsubscribeForm(request.GET) + form = UnsubscribeForm(request.GET.dict() | request.POST.dict()) if form.is_valid(): form.save() logger.info('Unsubscribed %s', form.cleaned_data['email']) return render(request, 'contacts/unsubscribe_success.html') else: - form = UnsubscribeForm() + form = UnsubscribeForm(request.GET.dict() | request.POST.dict()) return render(request, 'contacts/unsubscribe.html', {'form': form})