diff --git a/.travis.yml b/.travis.yml index 8c4237d..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 - all_branches: true + condition: $TOXENV = py27-django111-wagtail113 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', diff --git a/src/wagtail_personalisation/adapters.py b/src/wagtail_personalisation/adapters.py index 8d1ada8..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: @@ -184,12 +186,15 @@ 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) + 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 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) diff --git a/tests/unit/test_static_dynamic_segments.py b/tests/unit/test_static_dynamic_segments.py index b43a296..8b7181d 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,13 @@ 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() + assert user not in instance.excluded_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,7 +314,41 @@ 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() + assert user in instance.excluded_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 @@ -328,7 +364,9 @@ 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() + assert user in instance.excluded_users.all() @pytest.mark.django_db @@ -344,7 +382,9 @@ 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() + assert user not in instance.excluded_users.all() @pytest.mark.django_db @@ -361,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 @@ -377,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