diff --git a/core/models.py b/core/models.py index 5ea88182cd..bbf0abbcd5 100644 --- a/core/models.py +++ b/core/models.py @@ -73,6 +73,8 @@ SECTION_SLUGS as EXPORTPLAN_SLUGS, SECTIONS as EXPORTPLAN_URL_MAP, ) +from django.utils.decorators import method_decorator +from django.views.decorators.csrf import csrf_protect # If we make a Redirect appear as a Snippet, we can sync it via Wagtail-Transfer register_snippet(Redirect) @@ -764,7 +766,9 @@ def serve(self, request): return self._redirect_to_parent_module() -class DetailPage(settings.FEATURE_DEA_V2 and CMSGenericPageAnonymous or CMSGenericPage, mixins.HCSATMixin): +class DetailPage( + settings.FEATURE_DEA_V2 and CMSGenericPageAnonymous or CMSGenericPage, mixins.HCSATMixin, WagtailCacheMixin +): estimated_read_duration = models.DurationField(null=True, blank=True) parent_page_types = [ 'core.CuratedListPage', # TEMPORARY: remove after topics refactor migration has run @@ -1094,6 +1098,7 @@ def form_valid(self, form, request): return JsonResponse({'pk': hcsat.pk}) return HttpResponseRedirect(self.get_success_url(request)) + @method_decorator(csrf_protect, name='post') def serve(self, request, *args, **kwargs): self.handle_page_view(request) diff --git a/core/templatetags/clear_cache.py b/core/templatetags/clear_cache.py new file mode 100644 index 0000000000..95ec0abf95 --- /dev/null +++ b/core/templatetags/clear_cache.py @@ -0,0 +1,63 @@ +from django import template +from django.core.cache import cache +from urllib.parse import quote +from hashlib import md5 + +register = template.Library() + + +class ClearCacheNode(template.Node): + def __init__(self, fragment_name, vary_on): + self.fragment_name = fragment_name + self.vary_on = vary_on + + def render(self, context): + # Build a unicode key for this fragment and all vary-on's. + args = md5( + u':'.join([quote(template.Variable('request').resolve(var, context)) for var in self.vary_on]).encode( + 'utf-8' + ) + ) + from django.core.cache.utils import make_template_fragment_key + + cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest()) + print('clear-cache key', cache_key) + from django.core.cache import cache + + key = make_template_fragment_key('hcsat') + + result2 = cache.delete(key) + result1 = cache.delete_many(keys=cache.keys('*hcsat*')) + result = cache.delete(cache_key) + print('result', result) + print('result1', result1) + print('result2', result2) + return '' + + +@register.simple_tag +def clear_cache(parser, token): + """ + This will clear the cache for a template fragment + + Usage:: + + {% load clearcache %} + {% clearcache [fragment_name] %} + + This tag also supports varying by a list of arguments:: + + {% load clearcache %} + {% clearcache [fragment_name] [var1] [var2] .. %} + + The set of arguments must be the same as the original cache tag (except for expire_time). + """ + try: + tokens = token.split_contents() + print('tokens', tokens) + except ValueError: + raise template.TemplateSyntaxError('%r tag requires at least one argument' % token.contents.split()[0]) + return ClearCacheNode(tokens[1], tokens[2:]) + + +register.tag('clear_cache', clear_cache) diff --git a/core/urls.py b/core/urls.py index d63872c83a..1281bc0db7 100644 --- a/core/urls.py +++ b/core/urls.py @@ -175,6 +175,7 @@ def anonymous_user_required(function): name='campaign-site', ), path('api/signed-url/', views.SignedURLView.as_view(), name='signed-url'), + path('api/getcsrftoken/', views.CSRFView.as_view(), name='csrftoken'), # WHEN ADDING TO THIS LIST CONSIDER WHETHER YOU SHOULD ALSO ADD THE URL NAME # TO core.views.StaticViewSitemap ] diff --git a/core/views.py b/core/views.py index efb0f57665..b234975114 100644 --- a/core/views.py +++ b/core/views.py @@ -55,6 +55,9 @@ from domestic.models import DomesticDashboard, TopicLandingPage from export_academy.models import Event from sso.views import SSOBusinessUserLogoutView +from django.views.decorators.cache import never_cache +from django.middleware.csrf import get_token +from rest_framework.views import APIView logger = logging.getLogger(__name__) @@ -966,3 +969,16 @@ def get_context_data(self, **kwargs): ukea_events=ukea_events, market_guide=market_guide, ) + + +@method_decorator(never_cache, name='dispatch') +class CSRFView(APIView): + permission_classes = [] + authentication_classes = [] + + def _get_token(self, request): + token = get_token(request) + return JsonResponse(status=200, data={'csrftoken': token}) + + def dispatch(self, request, *args, **kwargs): + return self._get_token(request) diff --git a/domestic/static/javascript/hcsat-feedback-form.js b/domestic/static/javascript/hcsat-feedback-form.js index f23e3480af..76135df3be 100644 --- a/domestic/static/javascript/hcsat-feedback-form.js +++ b/domestic/static/javascript/hcsat-feedback-form.js @@ -31,10 +31,21 @@ class CsatFormHandler { this.resetForm(); } try { + const oldCsrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value + const csrfTokenFetch = await fetch('/api/getcsrftoken/', { + method: 'GET', + headers: { + 'cache_control': 'no-cache', + } + }) + const csrfTokenJson = await csrfTokenFetch.json() + const csrfToken = csrfTokenJson.csrftoken + console.log('csrfToken:',csrfToken) const response = await fetch(`${url}?js_enabled=True`, { method: 'POST', headers: { - 'X-CSRFToken': formData.get('csrfmiddlewaretoken'), + 'cache_control': 'no-cache', + 'X-CSRFToken': oldCsrfToken, 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest', }, diff --git a/learn/templates/learn/detail_page.html b/learn/templates/learn/detail_page.html index 6393bc9977..d9f02ce571 100644 --- a/learn/templates/learn/detail_page.html +++ b/learn/templates/learn/detail_page.html @@ -94,6 +94,11 @@

Explore the topic

{% endif %}
{% include 'learn/promo.html' with bg_class='great-bg-light-blue' %}
{% block body_inline_feedback %}{% endblock %} +{% load clear_cache %} +{% clear_cache hcsat %} +{% load cache %} +{% cache 500 hcsat %} +{% block hcsat %} {% if not csat_complete %}
@@ -103,6 +108,9 @@

Explore the topic

{% endif %} {% endblock %} +{% endcache %} +{% clear_cache hcsat %} +{% endblock %} {% block body_js %} {{ block.super }}