Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade to Wagtail 6.2 #8522

Merged
merged 14 commits into from
Sep 6, 2024
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
2 changes: 2 additions & 0 deletions .env_SAMPLE
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ export ALLOW_ADMIN_URL=True
#export AKAMAI_CLIENT_TOKEN=<akamai_client_token>
#export AKAMAI_FAST_PURGE_URL=<akamai_fast_purge_url>
#export AKAMAI_PURGE_ALL_URL=<akamai_purge_all_url>
#export AKAMAI_PURGE_HOSTNAMES=<akamai_purge_hostnames>

# export ENABLE_CLOUDFRONT_CACHE_PURGE=True
# export CLOUDFRONT_DISTRIBUTION_ID_FILES=<cloudfront_distribution_id_files_cf_gov>
# export CLOUDFRONT_PURGE_HOSTNAMES=<cloudfront_purge_hostnames>

#########################################################################
# Postgres Environment Vars.
Expand Down
Empty file added cfgov/cdntools/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions cfgov/cdntools/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.apps import AppConfig


class CDNToolsAppConfig(AppConfig):
name = "cdntools"
label = "cdntools"
verbose_name = "CDN Tools"

def ready(self):
import cdntools.signals # noqa: F401
42 changes: 13 additions & 29 deletions cfgov/v1/models/caching.py → cfgov/cdntools/backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,15 @@
import logging
import os

from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver

from wagtail.contrib.frontend_cache.backends import BaseBackend
from wagtail.contrib.frontend_cache.utils import PurgeBatch
from wagtail.documents.models import Document

import requests
from akamai.edgegrid import EdgeGridAuth

from v1.models.images import CFGOVRendition


logger = logging.getLogger(__name__)


class CDNHistory(models.Model):
created = models.DateTimeField(auto_now_add=True)
subject = models.CharField(max_length=2083)
message = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE)


class AkamaiBackend(BaseBackend):
"""Akamai backend that performs an 'invalidate' purge"""

Expand Down Expand Up @@ -132,19 +115,20 @@ def purge_all(self):
)


@receiver(post_save, sender=Document)
@receiver(post_save, sender=CFGOVRendition)
def cloudfront_cache_invalidation(sender, instance, **kwargs):
if not settings.ENABLE_CLOUDFRONT_CACHE_PURGE:
return
# This global will hold URLs that were purged by the MockCacheBackend for
# inspection.
MOCK_PURGED = []

if not instance.file:
return

url = instance.file.url
class MockCacheBackend(BaseBackend):
def __init__(self, params):
super().__init__(params)

def purge(self, url):
MOCK_PURGED.append(url)

logger.info(f'Purging {url} from "files" cache')
def purge_all(self):
MOCK_PURGED.append("__all__")

batch = PurgeBatch()
batch.add_url(url)
batch.purge(backends=["files"])
def purge_by_tags(self, tags, **kwargs):
MOCK_PURGED.extend(tags)
File renamed without changes.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def handle(self, *args, **options):
# Create settings specific to our deletion backend
delete_settings = {
"akamai_deleting": {
"BACKEND": "v1.models.caching.AkamaiDeletingBackend",
"BACKEND": "cdntools.backends.AkamaiDeletingBackend",
"CLIENT_TOKEN": global_settings["akamai"]["CLIENT_TOKEN"],
"CLIENT_SECRET": global_settings["akamai"]["CLIENT_SECRET"],
"ACCESS_TOKEN": global_settings["akamai"]["ACCESS_TOKEN"],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from django.conf import settings
from django.core.management.base import BaseCommand

from v1.models.caching import AkamaiBackend
from cdntools.backends import AkamaiBackend


class Command(BaseCommand):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,9 @@ def add_arguments(self, parser):

def handle(self, *args, **options):
batch = PurgeBatch()
batch.add_urls(options["url"])
batch.add_urls(
[
options["url"],
]
)
batch.purge()
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django.core.management.base import BaseCommand

from v1.signals import configure_akamai_backend
from wagtail.contrib.frontend_cache.utils import get_backends


class Command(BaseCommand):
Expand All @@ -26,5 +26,5 @@ def add_arguments(self, parser):
def handle(self, *args, **options):
cache_tags = options["cache_tag"]
action = options["action"]
backend = configure_akamai_backend()
backend.purge_by_tags(cache_tags, action=action)
akamai_backend = get_backends(backends="akamai")["akamai"]
akamai_backend.purge_by_tags(cache_tags, action=action)
36 changes: 36 additions & 0 deletions cfgov/cdntools/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 4.2.14 on 2024-08-20 12:49

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
# This model moved from v1. We'll reuse that existing table.
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.CreateModel(
name='CDNHistory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('subject', models.CharField(max_length=2083)),
('message', models.CharField(max_length=255)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'v1_cdnhistory',
},
),
],
database_operations=[],
)
]
Empty file.
12 changes: 12 additions & 0 deletions cfgov/cdntools/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.contrib.auth.models import User
from django.db import models


class CDNHistory(models.Model):
created = models.DateTimeField(auto_now_add=True)
subject = models.CharField(max_length=2083)
message = models.CharField(max_length=255)
user = models.ForeignKey(User, on_delete=models.CASCADE)

class Meta:
db_table = "v1_cdnhistory"
28 changes: 28 additions & 0 deletions cfgov/cdntools/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import logging

from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver

from wagtail.contrib.frontend_cache.utils import PurgeBatch
from wagtail.documents.models import Document


logger = logging.getLogger(__name__)


@receiver(post_save, sender=Document)
def cloudfront_cache_invalidation(sender, instance, **kwargs):
if not settings.ENABLE_CLOUDFRONT_CACHE_PURGE:
return

if not instance.file:
return

url = instance.file.url

logger.info(f'Purging {url} from "files" cache')

batch = PurgeBatch()
batch.add_url(url)
batch.purge(backends=["files"])
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@
from django.test import TestCase, override_settings

from wagtail.documents.models import Document
from wagtail.images.tests.utils import get_test_image_file

from core.testutils.mock_cache_backend import CACHE_PURGED_URLS
from v1.models.caching import (
from cdntools.backends import (
MOCK_PURGED,
AkamaiBackend,
AkamaiDeletingBackend,
cloudfront_cache_invalidation,
)
from v1.models.images import CFGOVImage
from cdntools.signals import cloudfront_cache_invalidation


class TestAkamaiBackend(TestCase):
Expand Down Expand Up @@ -161,7 +159,7 @@ def test_purge_all(self):
@override_settings(
WAGTAILFRONTENDCACHE={
"files": {
"BACKEND": "core.testutils.mock_cache_backend.MockCacheBackend",
"BACKEND": "cdntools.backends.MockCacheBackend",
},
},
)
Expand All @@ -172,35 +170,21 @@ def setUp(self):
self.document.file.save(
"example.txt", ContentFile("A boring example document")
)
self.image = CFGOVImage.objects.create(
title="test", file=get_test_image_file()
)
self.rendition = self.image.get_rendition("original")

CACHE_PURGED_URLS[:] = []
MOCK_PURGED[:] = []

def tearDown(self):
self.document.file.delete()

def test_rendition_saved_cache_purge_disabled(self):
cloudfront_cache_invalidation(None, self.rendition)
self.assertEqual(CACHE_PURGED_URLS, [])

def test_document_saved_cache_purge_disabled(self):
cloudfront_cache_invalidation(None, self.document)
self.assertEqual(CACHE_PURGED_URLS, [])
self.assertEqual(MOCK_PURGED, [])

@override_settings(ENABLE_CLOUDFRONT_CACHE_PURGE=True)
def test_document_saved_cache_purge_without_file(self):
cloudfront_cache_invalidation(None, self.document_without_file)
self.assertEqual(CACHE_PURGED_URLS, [])

@override_settings(ENABLE_CLOUDFRONT_CACHE_PURGE=True)
def test_rendition_saved_cache_invalidation(self):
cloudfront_cache_invalidation(None, self.rendition)
self.assertIn(self.rendition.file.url, CACHE_PURGED_URLS)
self.assertEqual(MOCK_PURGED, [])

@override_settings(ENABLE_CLOUDFRONT_CACHE_PURGE=True)
def test_document_saved_cache_invalidation(self):
cloudfront_cache_invalidation(None, self.document)
self.assertIn(self.document.file.url, CACHE_PURGED_URLS)
self.assertIn(self.document.file.url, MOCK_PURGED)
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,23 @@ class DeletePageCacheTestCase(TestCase):
@override_settings(
WAGTAILFRONTENDCACHE={
"akamai": {
"BACKEND": "v1.models.caching.AkamaiBackend",
"BACKEND": "cdntools.backends.AkamaiBackend",
"CLIENT_TOKEN": "fake",
"CLIENT_SECRET": "fake",
"ACCESS_TOKEN": "fake",
}
}
)
@mock.patch(
"v1.management.commands.delete_page_cache.purge_urls_from_cache"
"cdntools.management.commands.delete_page_cache.purge_urls_from_cache"
)
def test_submission_with_url_akamai(self, mock_purge):
call_command("delete_page_cache", url="https://server/foo/bar")
mock_purge.assert_called_once_with(
"https://server/foo/bar",
backend_settings={
"akamai_deleting": {
"BACKEND": "v1.models.caching.AkamaiDeletingBackend",
"BACKEND": "cdntools.backends.AkamaiDeletingBackend",
"CLIENT_TOKEN": "fake",
"CLIENT_SECRET": "fake",
"ACCESS_TOKEN": "fake",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
@override_settings(
WAGTAILFRONTENDCACHE={
"akamai": {
"BACKEND": "v1.models.caching.AkamaiBackend",
"BACKEND": "cdntools.backends.AkamaiBackend",
"CLIENT_TOKEN": "fake",
"CLIENT_SECRET": "fake",
"ACCESS_TOKEN": "fake",
}
}
)
class InvalidateAllPagesTestCase(TestCase):
@mock.patch("v1.models.caching.AkamaiBackend.purge_all")
@mock.patch("cdntools.backends.AkamaiBackend.purge_all")
def test_submission_with_url_akamai(self, mock_purge_all):
call_command("invalidate_all_pages_cache")
mock_purge_all.assert_any_call()
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.management import call_command
from django.test import TestCase, override_settings

from cdntools.backends import MOCK_PURGED


@override_settings(
WAGTAILFRONTENDCACHE={
"akamai": {
"BACKEND": "cdntools.backends.MockCacheBackend",
}
}
)
class InvalidatePageTestCase(TestCase):
def test_submission_with_url(self):
call_command("invalidate_page_cache", url="https://server/foo/bar")
self.assertIn("https://server/foo/bar", MOCK_PURGED)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.core.management import call_command
from django.test import TestCase, override_settings

from cdntools.backends import MOCK_PURGED


@override_settings(
WAGTAILFRONTENDCACHE={
"akamai": {
"BACKEND": "cdntools.backends.MockCacheBackend",
},
}
)
class CacheTagPurgeTestCase(TestCase):
def test_submission_with_url_akamai(self):
call_command(
"purge_by_cache_tags",
"--cache_tag",
"complaints",
"--action",
"invalidate",
)
self.assertIn("complaints", MOCK_PURGED)
Empty file.
Loading
Loading