From 597c0a50f057e6a1ce75258a71ea30eb14d1cec5 Mon Sep 17 00:00:00 2001 From: Jasper Berghoef Date: Wed, 11 Jan 2017 15:28:40 +0100 Subject: [PATCH] Device detection and accompanying rule Signed-off-by: Jasper Berghoef --- setup.py | 2 +- src/personalisation/models.py | 33 +++++++++++++++++++++++++++++++++ tests/factories/segment.py | 5 +++++ tests/unit/test_factories.py | 13 ++++++++++++- tests/unit/test_middleware.py | 26 ++++++++++++++++++++++++-- 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index de3bfdc..d63b3ee 100644 --- a/setup.py +++ b/setup.py @@ -2,8 +2,8 @@ from setuptools import find_packages, setup install_requires = [ - 'django-polymorphic==1.0.2', 'wagtail>=1.7', + 'user-agents>=1.0.1', ] tests_require = [ diff --git a/src/personalisation/models.py b/src/personalisation/models.py index dc0665a..426db55 100644 --- a/src/personalisation/models.py +++ b/src/personalisation/models.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, unicode_literals import re from datetime import datetime +from user_agents import parse from django.db import models from django.db.models.signals import pre_save @@ -228,6 +229,38 @@ class QueryRule(AbstractBaseRule): return _('Query Rule') +@python_2_unicode_compatible +class DeviceRule(AbstractBaseRule): + """Device rule to segment users based on matching devices""" + mobile = models.BooleanField(_("Mobile phone"), default=False) + tablet = models.BooleanField(_("Tablet"), default=False) + desktop = models.BooleanField(_("Desktop"), default=False) + + panels = [ + FieldPanel('mobile'), + FieldPanel('tablet'), + FieldPanel('desktop'), + ] + + def __init__(self, *args, **kwargs): + super(DeviceRule, self).__init__(*args, **kwargs) + + def test_user(self, request=None): + ua_header = request.META['HTTP_USER_AGENT'] + user_agent = parse(ua_header) + + if user_agent.is_mobile: + return self.mobile + elif user_agent.is_tablet: + return self.tablet + elif user_agent.is_pc: + return self.desktop + else: + return False + + def __str__(self): + return _('Device Rule') + @python_2_unicode_compatible class UserIsLoggedInRule(AbstractBaseRule): """User should be logged in""" diff --git a/tests/factories/segment.py b/tests/factories/segment.py index dddb3d1..aa1d2e2 100644 --- a/tests/factories/segment.py +++ b/tests/factories/segment.py @@ -48,3 +48,8 @@ class QueryRuleFactory(factory.DjangoModelFactory): class Meta: model = models.QueryRule + +class DeviceRuleFactory(factory.DjangoModelFactory): + + class Meta: + model = models.DeviceRule diff --git a/tests/unit/test_factories.py b/tests/unit/test_factories.py index 7193461..432f2ac 100644 --- a/tests/unit/test_factories.py +++ b/tests/unit/test_factories.py @@ -6,7 +6,7 @@ import pytest from personalisation.models import Segment, TimeRule from tests.factories.segment import ( - ReferralRuleFactory, SegmentFactory, TimeRuleFactory, DayRuleFactory) + ReferralRuleFactory, SegmentFactory, TimeRuleFactory, DayRuleFactory, DeviceRuleFactory) """Factory tests""" @pytest.mark.django_db @@ -39,6 +39,17 @@ def test_create_segment_with_day_rule(): assert day_rule.sun is False +"""DeviceRuleFactory tests""" +@pytest.mark.django_db +def test_create_segment_with_device_rule(): + segment = SegmentFactory(name='DeviceSegment') + device_rule = DeviceRuleFactory(mobile=True, segment=segment) + + assert device_rule.mobile is True + assert device_rule.tablet is False + assert device_rule.desktop is False + + """ReferralRuleFactory tests""" @pytest.mark.django_db def test_create_segment_with_referral_rule(): diff --git a/tests/unit/test_middleware.py b/tests/unit/test_middleware.py index 8ef13e6..83a112c 100644 --- a/tests/unit/test_middleware.py +++ b/tests/unit/test_middleware.py @@ -9,7 +9,7 @@ from wagtail.wagtailcore.models import Page from tests.factories.segment import ( QueryRuleFactory, ReferralRuleFactory, SegmentFactory, TimeRuleFactory, - DayRuleFactory, VisitCountRuleFactory) + DayRuleFactory, VisitCountRuleFactory, DeviceRuleFactory) from tests.factories.site import SiteFactory @@ -52,6 +52,28 @@ class TestUserSegmenting(object): assert client.session['segments'][0]['encoded_name'] == 'day-only' + def test_device_segment(self, client): + device_only_segment = SegmentFactory(name='Device only') + device_rule = DeviceRuleFactory( + tablet=True, + segment=device_only_segment) + + client.get('/', **{'HTTP_USER_AGENT': 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X)'}) + + assert client.session['segments'][0]['encoded_name'] == 'device-only' + + + def test_device_segment_no_match(self, client): + no_device_segment = SegmentFactory(name='No device') + device_rule = DeviceRuleFactory( + mobile=True, + segment=no_device_segment) + + client.get('/', **{'HTTP_USER_AGENT': 'Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X)'}) + + assert not client.session['segments'] + + def test_referral_segment(self, client): referral_segment = SegmentFactory(name='Referral') referral_rule = ReferralRuleFactory( @@ -59,7 +81,7 @@ class TestUserSegmenting(object): segment=referral_segment ) - client.get('/', **{ 'HTTP_REFERER': 'test.test'}) + client.get('/', **{'HTTP_REFERER': 'test.test'}) assert client.session['segments'][0]['encoded_name'] == 'referral'