7

Merge pull request #7 from praekeltfoundation/feature/SAS-85-calculate-matching-users

Count number of users that match static rules for a segment
This commit is contained in:
Kaitlyn
2018-01-26 08:58:00 +02:00
committed by GitHub
5 changed files with 203 additions and 3 deletions

View File

@ -1,5 +1,6 @@
from __future__ import absolute_import, unicode_literals
from datetime import datetime
from importlib import import_module
from itertools import takewhile
@ -26,6 +27,29 @@ def user_from_data(user_id):
class SegmentAdminForm(WagtailAdminModelForm):
def count_matching_users(self, rules, match_any):
""" Calculates how many users match the given static rules
"""
count = 0
static_rules = [rule for rule in rules if rule.static]
if not static_rules:
return count
User = get_user_model()
users = User.objects.all()
for user in users.iterator():
if match_any:
if any(rule.test_user(None, user) for rule in static_rules):
count += 1
elif all(rule.test_user(None, user) for rule in static_rules):
count += 1
return count
def clean(self):
cleaned_data = super(SegmentAdminForm, self).clean()
Segment = self._meta.model
@ -63,6 +87,16 @@ class SegmentAdminForm(WagtailAdminModelForm):
if not self.instance.is_static:
self.instance.count = 0
if is_new:
rules = [
form.instance for formset in self.formsets.values()
for form in formset
if form not in formset.deleted_forms
]
self.instance.matched_users_count = self.count_matching_users(
rules, self.instance.match_any)
self.instance.matched_count_updated_at = datetime.now()
instance = super(SegmentAdminForm, self).save(*args, **kwargs)
if is_new and instance.is_static and instance.all_rules_static:

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.9 on 2018-01-25 09:18
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wagtail_personalisation', '0015_static_users'),
]
operations = [
migrations.AddField(
model_name='segment',
name='matched_count_updated_at',
field=models.DateTimeField(editable=False, null=True),
),
migrations.AddField(
model_name='segment',
name='matched_users_count',
field=models.PositiveIntegerField(default=0, editable=False),
),
]

View File

@ -82,6 +82,9 @@ class Segment(ClusterableModel):
settings.AUTH_USER_MODEL,
)
matched_users_count = models.PositiveIntegerField(default=0, editable=False)
matched_count_updated_at = models.DateTimeField(null=True, editable=False)
objects = SegmentQuerySet.as_manager()
base_form_class = SegmentAdminForm

View File

@ -220,7 +220,12 @@ class VisitCountRule(AbstractBaseRule):
class Meta:
verbose_name = _('Visit count Rule')
def test_user(self, request):
def test_user(self, request, user=None):
if user:
# This rule currently does not support testing a user directly
# TODO: Make this test a user directly when the rule uses
# historical data
return False
operator = self.operator
segment_count = self.count
@ -276,7 +281,13 @@ class QueryRule(AbstractBaseRule):
class Meta:
verbose_name = _('Query Rule')
def test_user(self, request):
def test_user(self, request, user=None):
if user:
# This rule currently does not support testing a user directly
# TODO: Make this test a user directly if/when the rule uses
# historical data
return False
return request.GET.get(self.parameter, '') == self.value
def description(self):

View File

@ -8,7 +8,8 @@ from django.forms.models import model_to_dict
from tests.factories.segment import SegmentFactory
from wagtail_personalisation.forms import SegmentAdminForm
from wagtail_personalisation.models import Segment
from wagtail_personalisation.rules import TimeRule, VisitCountRule
from wagtail_personalisation.rules import (AbstractBaseRule, TimeRule,
VisitCountRule)
def form_with_data(segment, *rules):
@ -246,3 +247,129 @@ def test_dynamic_segment_with_non_static_rules_have_a_count():
)
form = form_with_data(segment, rule)
assert form.is_valid(), form.errors
@pytest.mark.django_db
def test_matched_user_count_added_to_segment_at_creation(site, client, mocker, django_user_model):
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second')
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
rule = VisitCountRule()
form = form_with_data(segment, rule)
mock_test_user = mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
instance = form.save()
assert mock_test_user.call_count == 2
instance.matched_users_count = 2
@pytest.mark.django_db
def test_count_users_matching_static_rules(site, client, django_user_model):
class TestStaticRule(AbstractBaseRule):
static = True
class Meta:
app_label = 'wagtail_personalisation'
def test_user(self, request, user):
return True
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second')
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
rule = TestStaticRule()
form = form_with_data(segment, rule)
assert form.count_matching_users([rule], True) is 2
@pytest.mark.django_db
def test_count_matching_users_only_counts_static_rules(site, client, django_user_model):
class TestStaticRule(AbstractBaseRule):
class Meta:
app_label = 'wagtail_personalisation'
def test_user(self, request, user):
return True
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second')
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
rule = TestStaticRule()
form = form_with_data(segment, rule)
assert form.count_matching_users([rule], True) is 0
@pytest.mark.django_db
def test_count_matching_users_handles_match_any(site, client, django_user_model):
class TestStaticRuleFirst(AbstractBaseRule):
static = True
class Meta:
app_label = 'wagtail_personalisation'
def test_user(self, request, user):
if user.username == 'first':
return True
return False
class TestStaticRuleSecond(AbstractBaseRule):
static = True
class Meta:
app_label = 'wagtail_personalisation'
def test_user(self, request, user):
if user.username == 'second':
return True
return False
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second')
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
first_rule = TestStaticRuleFirst()
second_rule = TestStaticRuleSecond()
form = form_with_data(segment, first_rule, second_rule)
assert form.count_matching_users([first_rule, second_rule], True) is 2
@pytest.mark.django_db
def test_count_matching_users_handles_match_all(site, client, django_user_model):
class TestStaticRuleFirst(AbstractBaseRule):
static = True
class Meta:
app_label = 'wagtail_personalisation'
def test_user(self, request, user):
if user.username == 'first':
return True
return False
class TestStaticRuleContainsS(AbstractBaseRule):
static = True
class Meta:
app_label = 'wagtail_personalisation'
def test_user(self, request, user):
if 's' in user.username:
return True
return False
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second')
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
first_rule = TestStaticRuleFirst()
s_rule = TestStaticRuleContainsS()
form = form_with_data(segment, first_rule, s_rule)
assert form.count_matching_users([first_rule, s_rule], False) is 1