From 2ae115c72f636f5b53940fbed39c41ba340e87c8 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Wed, 31 May 2017 12:25:43 +0100 Subject: [PATCH] Added visit page view Fixes #74 Allows frontend users to submit page views and retrieve segments while behind a cache or CDN. --- src/wagtail_personalisation/adapters.py | 4 +- src/wagtail_personalisation/views.py | 38 ++++++++++++- tests/sandbox/urls.py | 3 ++ tests/unit/test_visit_page_view.py | 71 +++++++++++++++++++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 tests/unit/test_visit_page_view.py diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index 0225ced..f8fae14 100644 --- a/src/wagtail_personalisation/adapters.py +++ b/src/wagtail_personalisation/adapters.py @@ -137,7 +137,7 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter): segments = self.get_segments() return next((s for s in segments if s.pk == segment_id), None) - def add_page_visit(self, page): + def add_page_visit(self, page, path=None): """Mark the page as visited by the user""" visit_count = self.request.session.setdefault('visit_count', []) page_visits = [visit for visit in visit_count if visit['id'] == page.pk] @@ -150,7 +150,7 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter): visit_count.append({ 'slug': page.slug, 'id': page.pk, - 'path': self.request.path, + 'path': path or self.request.path, 'count': 1, }) diff --git a/src/wagtail_personalisation/views.py b/src/wagtail_personalisation/views.py index 2aebb0c..847ce88 100644 --- a/src/wagtail_personalisation/views.py +++ b/src/wagtail_personalisation/views.py @@ -2,13 +2,16 @@ from __future__ import absolute_import, unicode_literals from django import forms -from django.http import HttpResponseForbidden, HttpResponseRedirect +from django.http import HttpResponseForbidden, HttpResponseRedirect, HttpResponseBadRequest, JsonResponse from django.shortcuts import get_object_or_404, reverse +from django.views.decorators.cache import never_cache +from django.views.decorators.http import require_POST from django.utils.translation import ugettext_lazy as _ from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register from wagtail.contrib.modeladmin.views import IndexView from wagtail.wagtailcore.models import Page +from wagtail_personalisation.adapters import get_segment_adapter from wagtail_personalisation.models import Segment @@ -137,3 +140,36 @@ def copy_page_view(request, page_id, segment_id): return HttpResponseRedirect(edit_url) return HttpResponseForbidden() + + +@never_cache +@require_POST +def visit_page(request): + """Allows a frontend user to submit a page view and retrieve their current + segments on a site that is behind a cache or CDN. + + On each page view, the user must POST to this view the page ID and path + that they are browsing. It will return a JSON-formatted document containing + a list of segments that are currently active for them. + """ + segment_adapter = get_segment_adapter(request) + page_id = request.POST.get('page_id') + path = request.POST.get('path') + + if page_id is None or path is None: + return HttpResponseBadRequest() + + page = Page.objects.filter(id=page_id).only('id', 'slug', 'live').first() + + if page is None or page.live is False: + return HttpResponseBadRequest() + + segment_adapter.add_page_visit(page, path=path) + segment_adapter.refresh() + + return JsonResponse({ + 'segments': [ + segment['encoded_name'] + for segment in segment_adapter.get_segments() + ] + }) diff --git a/tests/sandbox/urls.py b/tests/sandbox/urls.py index cb109ba..4fb748e 100644 --- a/tests/sandbox/urls.py +++ b/tests/sandbox/urls.py @@ -5,6 +5,7 @@ from django.contrib import admin from wagtail.wagtailadmin import urls as wagtailadmin_urls from wagtail.wagtailcore import urls as wagtail_urls from wagtail.wagtaildocs import urls as wagtaildocs_urls +from wagtail_personalisation.views import visit_page urlpatterns = [ url(r'^django-admin/', include(admin.site.urls)), @@ -12,5 +13,7 @@ urlpatterns = [ url(r'^admin/', include(wagtailadmin_urls)), url(r'^documents/', include(wagtaildocs_urls)), + url(r'^visit-page/', visit_page), + url(r'', include(wagtail_urls)), ] diff --git a/tests/unit/test_visit_page_view.py b/tests/unit/test_visit_page_view.py new file mode 100644 index 0000000..70dc91f --- /dev/null +++ b/tests/unit/test_visit_page_view.py @@ -0,0 +1,71 @@ +from __future__ import absolute_import, unicode_literals + +import pytest +from freezegun import freeze_time +from wagtail_factories import SiteFactory + +from tests.factories.rule import DayRuleFactory +from tests.factories.segment import SegmentFactory + + +@pytest.mark.django_db +class TestVisitPageView(object): + + def setup(self): + """ + Sets up a site root to test segmenting + """ + self.site = SiteFactory(is_default_site=True) + + def test_counts_visits(self, client): + response = client.post('/visit-page/', { + 'page_id': 1, + 'path': 'foo', + }) + + assert response.status_code == 200 + assert client.session['visit_count'] == [ + {'slug': 'root', 'id': 1, 'path': 'foo', 'count': 1} + ] + + @freeze_time("2017-01-01") + def test_returns_segments(self, client): + day_only_segment = SegmentFactory(name='Day only') + DayRuleFactory( + sun=True, + segment=day_only_segment) + + response = client.post('/visit-page/', { + 'page_id': 1, + 'path': 'foo', + }) + + assert response.status_code == 200 + assert response.json() == { + 'segments': ['day-only'] + } + + def test_get_request(self, client): + response = client.get('/visit-page/') + + assert response.status_code == 405 + + def test_missing_page_id(self, client): + response = client.post('/visit-page/') + + assert response.status_code == 400 + + def test_missing_path(self, client): + response = client.post('/visit-page/', { + 'page_id': 1, + }) + + assert response.status_code == 400 + + def test_nonexistent_page(self, client): + response = client.post('/visit-page/', { + 'page_id': 9999999, + 'path': 'foo', + }) + + assert response.status_code == 400