From 6b1a7cf1f278f30aca844fb36ce3ceb8fc10badc Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Tue, 6 Feb 2018 16:11:20 +0200 Subject: [PATCH 1/9] Only deploy for one environment per travis build --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c4237d..305772f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,4 +28,4 @@ deploy: secure: IxPnc95OFNQsl7kFfLlLc38KfHh/W79VXnhEkdb2x1GZznOsG167QlhpAnyXIJysCQxgMMwLMuPOOdk1WIxOpGNM1/M80hNzPAfxMYWPuwposDdwoIc8SyREPJt16BXWAY+rAH8SHxld9p1YdRbOEPSSglODe4dCmQWsCbKjV3aKv+gZxBvf6pMxUglp2fBIlCwrE77MyMYh9iW0AGzD6atC2xN9NgAm4f+2WFlKCUL/MztLhNWdvWEiibQav6Tts7p8tWrsBVzywDW2IOy3O0ihPgRdISZ7QrxhiJTjlHYPAy4eRGOnYSQXvp6Dy8ONE5a6Uv5g3Xw2UmQo85sSMUs2VxR0G7d+PQgL0A7ENBQ5i7eSAFHYs8EswiGilW2A7mM4qRXwg9URLelYSdkM+aNXvR+25dCsXakiO4NjCz/BPgNzxPeQLlBdxR5vHugeM/XYuhy6CHlZrR/uueaO0X8RyhJcqeDjBy58IrwYS3Mpj7QCfBpQ9PqsqXEWV9BKwKiBXM2+3hkhawFDWa0GW2PDbORKtSLy/ORfGLx5Y9qxQYKEGvFQA3iqkTjrLj+KeUziKtuvEAcvsdBIJVIxeoHwdl+qqxEm8A7YuRBnWVxWc3jE6wBXboeFP92gVe/ueoXmY22riK9Ja0pli3TyNga8by9WM8Qp4D2ZqkVXHwg= on: tags: true - all_branches: true + condition: $TOXENV=py27-django111-wagtail113 From 3017f32b6b45a2dc3c8caa8eb1e66cdef860bf1c Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Tue, 6 Feb 2018 16:18:16 +0200 Subject: [PATCH 2/9] Add spaces around = in bash deploy condition --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 305772f..3a78086 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,4 +28,4 @@ deploy: secure: IxPnc95OFNQsl7kFfLlLc38KfHh/W79VXnhEkdb2x1GZznOsG167QlhpAnyXIJysCQxgMMwLMuPOOdk1WIxOpGNM1/M80hNzPAfxMYWPuwposDdwoIc8SyREPJt16BXWAY+rAH8SHxld9p1YdRbOEPSSglODe4dCmQWsCbKjV3aKv+gZxBvf6pMxUglp2fBIlCwrE77MyMYh9iW0AGzD6atC2xN9NgAm4f+2WFlKCUL/MztLhNWdvWEiibQav6Tts7p8tWrsBVzywDW2IOy3O0ihPgRdISZ7QrxhiJTjlHYPAy4eRGOnYSQXvp6Dy8ONE5a6Uv5g3Xw2UmQo85sSMUs2VxR0G7d+PQgL0A7ENBQ5i7eSAFHYs8EswiGilW2A7mM4qRXwg9URLelYSdkM+aNXvR+25dCsXakiO4NjCz/BPgNzxPeQLlBdxR5vHugeM/XYuhy6CHlZrR/uueaO0X8RyhJcqeDjBy58IrwYS3Mpj7QCfBpQ9PqsqXEWV9BKwKiBXM2+3hkhawFDWa0GW2PDbORKtSLy/ORfGLx5Y9qxQYKEGvFQA3iqkTjrLj+KeUziKtuvEAcvsdBIJVIxeoHwdl+qqxEm8A7YuRBnWVxWc3jE6wBXboeFP92gVe/ueoXmY22riK9Ja0pli3TyNga8by9WM8Qp4D2ZqkVXHwg= on: tags: true - condition: $TOXENV=py27-django111-wagtail113 + condition: $TOXENV = py27-django111-wagtail113 From d114bb2570311450ebd71c4fdf4e86aa3d651e2d Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Thu, 8 Feb 2018 19:47:35 +0200 Subject: [PATCH 3/9] Always add the segment to the session if they pass --- src/wagtail_personalisation/adapters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index 8d1ada8..1012e7d 100644 --- a/src/wagtail_personalisation/adapters.py +++ b/src/wagtail_personalisation/adapters.py @@ -184,12 +184,12 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter): result = self._test_rules(segment_rules, self.request, match_any=segment.match_any) + if result and segment.randomise_into_segment(): if segment.is_static and not segment.is_full: if self.request.user.is_authenticated(): segment.static_users.add(self.request.user) - else: - additional_segments.append(segment) + additional_segments.append(segment) self.set_segments(current_segments + additional_segments) self.update_visit_count() From 824e42174fce6e5c9ad98328dfcd980a57d3ae37 Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Thu, 8 Feb 2018 19:48:31 +0200 Subject: [PATCH 4/9] Tests --- tests/unit/test_static_dynamic_segments.py | 40 ++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/tests/unit/test_static_dynamic_segments.py b/tests/unit/test_static_dynamic_segments.py index b43a296..b7aae7c 100644 --- a/tests/unit/test_static_dynamic_segments.py +++ b/tests/unit/test_static_dynamic_segments.py @@ -282,7 +282,7 @@ def test_randomisation_percentage_max_100(site, client, mocker, django_user_mode @pytest.mark.django_db -def test_in_segment_if_random_is_below_percentage(site, client, mocker, user): +def test_in_static_segment_if_random_is_below_percentage(site, client, mocker, user): segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1, randomisation_percent=40) rule = VisitCountRule(counted_page=site.root_page) @@ -295,11 +295,12 @@ def test_in_segment_if_random_is_below_percentage(site, client, mocker, user): client.force_login(user) client.get(site.root_page.url) + assert instance.id == client.session['segments'][0]['id'] assert user in instance.static_users.all() @pytest.mark.django_db -def test_not_in_segment_if_random_is_above_percentage(site, client, mocker, user): +def test_not_in_static_segment_if_random_is_above_percentage(site, client, mocker, user): segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1, randomisation_percent=40) rule = VisitCountRule(counted_page=site.root_page) @@ -312,9 +313,42 @@ def test_not_in_segment_if_random_is_above_percentage(site, client, mocker, user client.force_login(user) client.get(site.root_page.url) + assert len(client.session['segments']) == 0 assert user not in instance.static_users.all() +@pytest.mark.django_db +def test_offered_dynamic_segment_if_random_is_below_percentage(site, client, mocker): + segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC, + randomisation_percent=40) + rule = VisitCountRule(counted_page=site.root_page) + form = form_with_data(segment, rule) + instance = form.save() + + mocker.patch('random.randint', return_value=39) + session = client.session + session.save() + client.get(site.root_page.url) + + assert instance.id == client.session['segments'][0]['id'] + + +@pytest.mark.django_db +def test_not_offered_dynamic_segment_if_random_is_above_percentage(site, client, mocker): + segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC, + randomisation_percent=40) + rule = VisitCountRule(counted_page=site.root_page) + form = form_with_data(segment, rule) + form.save() + + mocker.patch('random.randint', return_value=41) + session = client.session + session.save() + client.get(site.root_page.url) + + assert len(client.session['segments']) == 0 + + @pytest.mark.django_db def test_not_in_segment_if_percentage_is_0(site, client, mocker, user): segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1, @@ -328,6 +362,7 @@ def test_not_in_segment_if_percentage_is_0(site, client, mocker, user): client.force_login(user) client.get(site.root_page.url) + assert len(client.session['segments']) == 0 assert user not in instance.static_users.all() @@ -344,6 +379,7 @@ def test_always_in_segment_if_percentage_is_100(site, client, mocker, user): client.force_login(user) client.get(site.root_page.url) + assert instance.id == client.session['segments'][0]['id'] assert user in instance.static_users.all() From 3162191a16950f1ebf76e5f32caeb6d9052dba49 Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Fri, 9 Feb 2018 12:32:42 +0200 Subject: [PATCH 5/9] Add field to segment to store excluded users --- .../migrations/0018_segment_excluded_users.py | 22 +++++++++++++++++++ src/wagtail_personalisation/models.py | 6 +++++ 2 files changed, 28 insertions(+) create mode 100644 src/wagtail_personalisation/migrations/0018_segment_excluded_users.py diff --git a/src/wagtail_personalisation/migrations/0018_segment_excluded_users.py b/src/wagtail_personalisation/migrations/0018_segment_excluded_users.py new file mode 100644 index 0000000..bafa477 --- /dev/null +++ b/src/wagtail_personalisation/migrations/0018_segment_excluded_users.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.9 on 2018-02-09 08:28 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('wagtail_personalisation', '0017_segment_randomisation_percent'), + ] + + operations = [ + migrations.AddField( + model_name='segment', + name='excluded_users', + field=models.ManyToManyField(help_text='Users that matched the rules but were excluded from the segment for some reason e.g. randomisation', related_name='excluded_segments', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/wagtail_personalisation/models.py b/src/wagtail_personalisation/models.py index 48d176b..7a4063e 100644 --- a/src/wagtail_personalisation/models.py +++ b/src/wagtail_personalisation/models.py @@ -83,6 +83,12 @@ class Segment(ClusterableModel): static_users = models.ManyToManyField( settings.AUTH_USER_MODEL, ) + excluded_users = models.ManyToManyField( + settings.AUTH_USER_MODEL, + help_text=_("Users that matched the rules but were excluded from the " + "segment for some reason e.g. randomisation"), + related_name="excluded_segments" + ) matched_users_count = models.PositiveIntegerField(default=0, editable=False) matched_count_updated_at = models.DateTimeField(null=True, editable=False) From 56a8e106d8ce75596c4264ad99f5ecc7edda55c1 Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Fri, 9 Feb 2018 12:35:09 +0200 Subject: [PATCH 6/9] Add users excluded by randomisation to excluded_users list --- src/wagtail_personalisation/adapters.py | 3 +++ src/wagtail_personalisation/forms.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index 1012e7d..a4222f7 100644 --- a/src/wagtail_personalisation/adapters.py +++ b/src/wagtail_personalisation/adapters.py @@ -190,6 +190,9 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter): if self.request.user.is_authenticated(): segment.static_users.add(self.request.user) additional_segments.append(segment) + elif result: + if self.request.user.is_authenticated(): + segment.excluded_users.add(self.request.user) self.set_segments(current_segments + additional_segments) self.update_visit_count() diff --git a/src/wagtail_personalisation/forms.py b/src/wagtail_personalisation/forms.py index 6183f4c..92f5f8e 100644 --- a/src/wagtail_personalisation/forms.py +++ b/src/wagtail_personalisation/forms.py @@ -107,6 +107,7 @@ class SegmentAdminForm(WagtailAdminModelForm): adapter = get_segment_adapter(request) users_to_add = [] + users_to_exclude = [] sessions = Session.objects.iterator() take_session = takewhile( lambda x: instance.count == 0 or len(users_to_add) <= instance.count, @@ -121,8 +122,11 @@ class SegmentAdminForm(WagtailAdminModelForm): passes = adapter._test_rules(instance.get_rules(), request, instance.match_any) if passes and instance.randomise_into_segment(): users_to_add.append(user) + elif passes: + users_to_exclude.append(user) instance.static_users.add(*users_to_add) + instance.excluded_users.add(*users_to_exclude) return instance From 521222f748d547dfed30b7f48f7a4f5b1b110099 Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Fri, 9 Feb 2018 12:35:53 +0200 Subject: [PATCH 7/9] Don't check if excluded users match segment rules --- src/wagtail_personalisation/adapters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index a4222f7..5001fac 100644 --- a/src/wagtail_personalisation/adapters.py +++ b/src/wagtail_personalisation/adapters.py @@ -177,6 +177,8 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter): for segment in enabled_segments: if segment.is_static and segment.static_users.filter(id=self.request.user.id).exists(): additional_segments.append(segment) + elif segment.excluded_users.filter(id=self.request.user.id).exists(): + continue elif not segment.is_static or not segment.is_full: segment_rules = [] for rule_model in rule_models: From 0f18024ebc7fa6262cb4605844562dd1d7722187 Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Fri, 9 Feb 2018 12:36:34 +0200 Subject: [PATCH 8/9] Tests --- tests/unit/test_static_dynamic_segments.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/unit/test_static_dynamic_segments.py b/tests/unit/test_static_dynamic_segments.py index b7aae7c..8b7181d 100644 --- a/tests/unit/test_static_dynamic_segments.py +++ b/tests/unit/test_static_dynamic_segments.py @@ -297,6 +297,7 @@ def test_in_static_segment_if_random_is_below_percentage(site, client, mocker, u assert instance.id == client.session['segments'][0]['id'] assert user in instance.static_users.all() + assert user not in instance.excluded_users.all() @pytest.mark.django_db @@ -315,6 +316,7 @@ def test_not_in_static_segment_if_random_is_above_percentage(site, client, mocke assert len(client.session['segments']) == 0 assert user not in instance.static_users.all() + assert user in instance.excluded_users.all() @pytest.mark.django_db @@ -364,6 +366,7 @@ def test_not_in_segment_if_percentage_is_0(site, client, mocker, user): assert len(client.session['segments']) == 0 assert user not in instance.static_users.all() + assert user in instance.excluded_users.all() @pytest.mark.django_db @@ -381,6 +384,7 @@ def test_always_in_segment_if_percentage_is_100(site, client, mocker, user): assert instance.id == client.session['segments'][0]['id'] assert user in instance.static_users.all() + assert user not in instance.excluded_users.all() @pytest.mark.django_db @@ -397,6 +401,7 @@ def test_not_added_to_static_segment_at_creation_if_random_above_percent(site, c instance = form.save() assert user not in instance.static_users.all() + assert user in instance.excluded_users.all() @pytest.mark.django_db @@ -413,6 +418,31 @@ def test_added_to_static_segment_at_creation_if_random_below_percent(site, clien instance = form.save() assert user in instance.static_users.all() + assert user not in instance.excluded_users.all() + + +@pytest.mark.django_db +def test_rules_check_skipped_if_user_in_excluded(site, client, mocker, user): + segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1, + randomisation_percent=100) + rule = VisitCountRule(counted_page=site.root_page) + form = form_with_data(segment, rule) + instance = form.save() + instance.excluded_users.add(user) + instance.save + + mock_test_rule = mocker.patch( + 'wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules') + + session = client.session + session.save() + client.force_login(user) + client.get(site.root_page.url) + + assert mock_test_rule.call_count == 0 + assert len(client.session['segments']) == 0 + assert user not in instance.static_users.all() + assert user in instance.excluded_users.all() @pytest.mark.django_db From 33277a0b2045edefbce6e24b9bb6b2a4184203f5 Mon Sep 17 00:00:00 2001 From: Kaitlyn Crawford Date: Fri, 9 Feb 2018 16:59:26 +0200 Subject: [PATCH 9/9] Version 0.10.7 --- CHANGES | 6 ++++++ docs/conf.py | 4 ++-- setup.cfg | 2 +- setup.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 74234b8..e3543bf 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +0.10.7 +================== + - Bug Fix: Ensure static segment members are show the survey immediately + - Records users excluded by randomisation on the segment + - Don't re-check excluded users + 0.10.6 ================== - Accepts and stores randomisation percentage for segment diff --git a/docs/conf.py b/docs/conf.py index f282bd7..567a811 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,10 +55,10 @@ author = 'Lab Digital BV' # built documents. # # The short X.Y version. -version = '0.10.6' +version = '0.10.7' # The full version, including alpha/beta/rc tags. -release = '0.10.6' +release = '0.10.7' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/setup.cfg b/setup.cfg index 34a404c..a6a243b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.10.6 +current_version = 0.10.7 commit = true tag = true tag_name = {new_version} diff --git a/setup.py b/setup.py index c180248..b783cfe 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ with open('README.rst') as fh: setup( name='wagtail-personalisation-molo', - version='0.10.6', + version='0.10.7', description='A forked version of Wagtail add-on for showing personalized content', author='Praekelt.org', author_email='dev@praekeltfoundation.org',