Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion apps/minutes/models.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
"""Models for PSF board meeting minutes."""

from django.conf import settings
from django.db import models
from django.db import models, transaction
from django.db.models.signals import post_delete, post_save, pre_save
from django.dispatch import receiver
from django.urls import reverse
from markupfield.fields import MarkupField

from apps.cms.models import ContentManageable
from apps.minutes.managers import MinutesQuerySet
from fastly.utils import purge_url

DEFAULT_MARKUP_TYPE = getattr(settings, "DEFAULT_MARKUP_TYPE", "restructuredtext")

Expand Down Expand Up @@ -53,3 +56,39 @@ def get_date_month(self):
def get_date_day(self):
"""Return the meeting date's zero-padded day string."""
return self.date.strftime("%d").zfill(2)


@receiver(pre_save, sender=Minutes)
def remember_previous_url(sender, instance, **kwargs):
"""Remember the stored detail URL so a changed meeting date also purges the old page."""
instance.previous_url = None
if kwargs.get("raw", False) or instance.pk is None:
return
previous = Minutes.objects.filter(pk=instance.pk).only("date").first()
if previous is not None:
instance.previous_url = previous.get_absolute_url()


@receiver(post_save, sender=Minutes)
@receiver(post_delete, sender=Minutes)
def purge_fastly_cache(sender, instance, **kwargs):
"""Purge the minutes detail, list, and feed pages once the change is committed.

Purges regardless of publish state so unpublished or deleted minutes disappear too.
Deferred to commit so a request racing the admin transaction can't re-cache stale content.
"""
# Skip in fixtures
if kwargs.get("raw", False):
return

current_url = instance.get_absolute_url()
urls = [current_url, reverse("minutes_list"), reverse("minutes_feed")]
previous_url = getattr(instance, "previous_url", None)
if previous_url and previous_url != current_url:
urls.append(previous_url)

def purge():
for url in urls:
purge_url(url)

transaction.on_commit(purge, using=kwargs["using"])
39 changes: 39 additions & 0 deletions apps/minutes/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
from unittest import mock

from django.test import TestCase

Expand Down Expand Up @@ -29,3 +30,41 @@ def test_date_methods(self):
self.assertEqual(self.m1.get_date_year(), "2012")
self.assertEqual(self.m1.get_date_month(), "01")
self.assertEqual(self.m1.get_date_day(), "01")


@mock.patch("apps.minutes.models.purge_url")
class MinutesPurgeTests(TestCase):
def purged(self, purge_url):
return {c.args[0] for c in purge_url.call_args_list}

def test_save_purges_detail_list_and_feed(self, purge_url):
with self.captureOnCommitCallbacks(execute=True):
Minutes.objects.create(date=datetime.date(2024, 3, 5), content="x", is_published=True)
self.assertEqual(
self.purged(purge_url),
{
"/psf/records/board/minutes/2024-03-05/",
"/psf/records/board/minutes/",
"/psf/records/board/minutes/feed/",
},
)

def test_no_purge_before_commit(self, purge_url):
with self.captureOnCommitCallbacks(execute=False) as callbacks:
Minutes.objects.create(date=datetime.date(2024, 3, 5), content="x", is_published=True)
purge_url.assert_not_called()
self.assertEqual(len(callbacks), 1)

def test_changing_date_purges_old_and_new_detail(self, purge_url):
minutes = Minutes.objects.create(date=datetime.date(2024, 3, 5), content="x", is_published=True)
minutes.date = datetime.date(2024, 3, 6)
with self.captureOnCommitCallbacks(execute=True):
minutes.save()
self.assertIn("/psf/records/board/minutes/2024-03-05/", self.purged(purge_url))
self.assertIn("/psf/records/board/minutes/2024-03-06/", self.purged(purge_url))

def test_delete_purges_detail(self, purge_url):
minutes = Minutes.objects.create(date=datetime.date(2024, 3, 5), content="x", is_published=True)
with self.captureOnCommitCallbacks(execute=True):
minutes.delete()
self.assertIn("/psf/records/board/minutes/2024-03-05/", self.purged(purge_url))
Loading