Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
d7c07cb238 | |||
6e83366df6 | |||
55364f8906 | |||
4fd0b30c66 | |||
c909852b08 | |||
ea1ecc2a98 | |||
0f0aecf673 | |||
c11960f921 | |||
37d49dcdfb | |||
869237360d | |||
33277a0b20 | |||
2cd643fb2d | |||
0f18024ebc | |||
521222f748 | |||
56a8e106d8 | |||
3162191a16 | |||
8c7e99313b | |||
824e42174f | |||
d114bb2570 | |||
7bba1e57cc | |||
3017f32b6b | |||
6b1a7cf1f2 | |||
1525b7946c |
@ -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
|
||||
|
11
CHANGES
11
CHANGES
@ -1,3 +1,14 @@
|
||||
0.10.8
|
||||
==================
|
||||
- Don't add users to exclude list for dynamic segments
|
||||
- Store segments a user is excluded from in the session
|
||||
|
||||
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
|
||||
|
@ -55,10 +55,10 @@ author = 'Lab Digital BV'
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '0.10.6'
|
||||
version = '0.10.8'
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.10.6'
|
||||
release = '0.10.8'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 0.10.6
|
||||
current_version = 0.10.8
|
||||
commit = true
|
||||
tag = true
|
||||
tag_name = {new_version}
|
||||
|
2
setup.py
2
setup.py
@ -32,7 +32,7 @@ with open('README.rst') as fh:
|
||||
|
||||
setup(
|
||||
name='wagtail-personalisation-molo',
|
||||
version='0.10.6',
|
||||
version='0.10.8',
|
||||
description='A forked version of Wagtail add-on for showing personalized content',
|
||||
author='Praekelt.org',
|
||||
author_email='dev@praekeltfoundation.org',
|
||||
|
@ -66,17 +66,21 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
||||
self.request.session.setdefault('segments', [])
|
||||
self._segment_cache = None
|
||||
|
||||
def get_segments(self):
|
||||
def get_segments(self, key="segments"):
|
||||
"""Return the persistent segments stored in the request session.
|
||||
|
||||
:param key: The key under which the segments are stored
|
||||
:type key: String
|
||||
:returns: The segments in the request session
|
||||
:rtype: list of wagtail_personalisation.models.Segment or empty list
|
||||
|
||||
"""
|
||||
if self._segment_cache is not None:
|
||||
if key == "segments" and self._segment_cache is not None:
|
||||
return self._segment_cache
|
||||
|
||||
raw_segments = self.request.session['segments']
|
||||
if key not in self.request.session:
|
||||
return []
|
||||
raw_segments = self.request.session[key]
|
||||
segment_ids = [segment['id'] for segment in raw_segments]
|
||||
|
||||
segments = (
|
||||
@ -86,14 +90,17 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
||||
.in_bulk(segment_ids))
|
||||
|
||||
retval = [segments[pk] for pk in segment_ids if pk in segments]
|
||||
self._segment_cache = retval
|
||||
if key == "segments":
|
||||
self._segment_cache = retval
|
||||
return retval
|
||||
|
||||
def set_segments(self, segments):
|
||||
def set_segments(self, segments, key="segments"):
|
||||
"""Set the currently active segments
|
||||
|
||||
:param segments: The segments to set for the current request
|
||||
:type segments: list of wagtail_personalisation.models.Segment
|
||||
:param key: The key under which to store the segments. Optional
|
||||
:type key: String
|
||||
|
||||
"""
|
||||
cache_segments = []
|
||||
@ -108,8 +115,9 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
||||
serialized_segments.append(serialized)
|
||||
segment_ids.add(segment.pk)
|
||||
|
||||
self.request.session['segments'] = serialized_segments
|
||||
self._segment_cache = cache_segments
|
||||
self.request.session[key] = serialized_segments
|
||||
if key == "segments":
|
||||
self._segment_cache = cache_segments
|
||||
|
||||
def get_segment_by_id(self, segment_id):
|
||||
"""Find and return a single segment from the request session.
|
||||
@ -171,12 +179,16 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
|
||||
rule_models = AbstractBaseRule.get_descendant_models()
|
||||
|
||||
current_segments = self.get_segments()
|
||||
excluded_segments = self.get_segments("excluded_segments")
|
||||
|
||||
# Run tests on all remaining enabled segments to verify applicability.
|
||||
additional_segments = []
|
||||
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() or
|
||||
segment in excluded_segments):
|
||||
continue
|
||||
elif not segment.is_static or not segment.is_full:
|
||||
segment_rules = []
|
||||
for rule_model in rule_models:
|
||||
@ -184,14 +196,20 @@ 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)
|
||||
additional_segments.append(segment)
|
||||
elif result:
|
||||
if segment.is_static and self.request.user.is_authenticated():
|
||||
segment.excluded_users.add(self.request.user)
|
||||
else:
|
||||
additional_segments.append(segment)
|
||||
excluded_segments += [segment]
|
||||
|
||||
self.set_segments(current_segments + additional_segments)
|
||||
self.set_segments(excluded_segments, "excluded_segments")
|
||||
self.update_visit_count()
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
@ -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)
|
||||
|
@ -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,43 @@ 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 len(client.session['excluded_segments']) == 0
|
||||
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)
|
||||
instance = 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
|
||||
assert instance.id == client.session['excluded_segments'][0]['id']
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@ -328,7 +366,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 +384,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 +403,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 +420,55 @@ 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
|
||||
def test_rules_check_skipped_if_dynamic_segment_in_excluded(site, client, mocker, user):
|
||||
segment = SegmentFactory.build(type=Segment.TYPE_DYNAMIC,
|
||||
randomisation_percent=100)
|
||||
rule = VisitCountRule(counted_page=site.root_page)
|
||||
form = form_with_data(segment, rule)
|
||||
instance = form.save()
|
||||
instance.persistent = True
|
||||
instance.save()
|
||||
|
||||
session = client.session
|
||||
session['excluded_segments'] = [{'id': instance.pk}]
|
||||
session.save()
|
||||
|
||||
mock_test_rule = mocker.patch(
|
||||
'wagtail_personalisation.adapters.SessionSegmentsAdapter._test_rules')
|
||||
|
||||
client.force_login(user)
|
||||
client.get(site.root_page.url)
|
||||
|
||||
assert mock_test_rule.call_count == 0
|
||||
assert len(client.session['segments']) == 0
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
|
Reference in New Issue
Block a user