7

Compare commits

...

80 Commits

Author SHA1 Message Date
cc1dd337bb Merge branch 'release/0.11.0' 2018-02-23 17:02:34 +02:00
a677846ff7 Bump version to 0.11.0 2018-02-23 17:02:17 +02:00
7d7861b862 Merge pull request #22 from praekeltfoundation/feature/SAS-72-download-cvs-of-user-data
Download CSV of users in a segment
2018-02-23 16:50:52 +02:00
8e854d0abe Merge branch 'develop' into feature/SAS-72-download-cvs-of-user-data 2018-02-23 14:05:31 +02:00
0051061d96 Merge pull request #21 from praekeltfoundation/feature/SAS-72-get-user-info-for-visit-count-rule
Get user info from visit count rule
2018-02-23 14:02:15 +02:00
f898dfe017 Actually add tests for segment users view 2018-02-22 16:12:24 +02:00
8ced5bd81c Fix flake8 2018-02-22 15:15:01 +02:00
9a86b0c8cc Add tests for segment users view 2018-02-22 15:14:34 +02:00
9408f90789 Use mock for testing matching user count
The fake class was causing other tests to fail because it inherits from AbstractBaseRule but isn't in the database.
I removed it and replaced it with mocked calls
2018-02-22 14:23:14 +02:00
ba6056e3f8 Link to download CSV of users in segment 2018-02-22 14:21:43 +02:00
fdc0a7f2e1 Get user info for Visit count rule 2018-02-21 19:08:29 +02:00
12b0cd9231 Make visit count session retrieval seperate method 2018-02-21 19:07:35 +02:00
330557be8d Make VisitCountRule.test_user actually test with only a user 2018-02-21 18:48:44 +02:00
aa917dee9c Merge pull request #20 from praekeltfoundation/feature/SAS-90-revert-query-rule-to-not-be-static
Remove static flag for Query Rule
2018-02-21 15:54:36 +02:00
364cb1a7e6 Query rule should not be static 2018-02-20 15:08:12 +02:00
bedbe06c65 Merge branch 'release/0.10.9' into develop 2018-02-13 14:17:55 +02:00
362f15e5ff version bump 0.10.9 2018-02-13 14:17:29 +02:00
8a0dba2efb Merge pull request #18 from praekeltfoundation/feature/issue-18-Display-the-number-of-users-in-a-static-segment-SAS-99
Display the number of users in a static segment SAS-99
2018-02-13 14:11:56 +02:00
59f4877e04 add localize filter 2018-02-13 13:57:00 +02:00
2ff29cc375 get the number of users in a static segment from static_users variable 2018-02-13 13:47:45 +02:00
8527e6ff23 Merge tag '0.10.8' into develop
Don't add users to exclude list for dynamic segments
Store segments a user is excluded from in the session
2018-02-13 10:13:24 +02:00
d7c07cb238 Merge branch 'release/0.10.8' 2018-02-13 10:12:45 +02:00
6e83366df6 Bump version to 0.10.8 2018-02-13 10:12:35 +02:00
55364f8906 Merge pull request #16 from praekeltfoundation/feature/SAS-78-fix-sampling-dynamic-segments
Fix sampling for Dynamic segments
2018-02-13 09:59:28 +02:00
4fd0b30c66 Check rules test skipped if segment excluded by session 2018-02-12 18:56:13 +02:00
c909852b08 Add tests 2018-02-12 18:01:01 +02:00
ea1ecc2a98 Get excluded segments from session and don't check them again 2018-02-12 18:00:38 +02:00
0f0aecf673 Store excluded segments in the session object 2018-02-12 17:57:36 +02:00
c11960f921 Only store excluded users for static segments 2018-02-12 16:58:20 +02:00
37d49dcdfb Merge tag '0.10.7' into develop
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
2018-02-09 17:01:02 +02:00
869237360d Merge branch 'release/0.10.7' 2018-02-09 17:00:09 +02:00
33277a0b20 Version 0.10.7 2018-02-09 16:59:26 +02:00
2cd643fb2d Merge pull request #15 from praekeltfoundation/feature/SAS-78-store-excluded-users-on-segment
Store users excluded by randomisation
2018-02-09 16:52:44 +02:00
0f18024ebc Tests 2018-02-09 12:36:34 +02:00
521222f748 Don't check if excluded users match segment rules 2018-02-09 12:35:53 +02:00
56a8e106d8 Add users excluded by randomisation to excluded_users list 2018-02-09 12:35:09 +02:00
3162191a16 Add field to segment to store excluded users 2018-02-09 12:32:42 +02:00
8c7e99313b Merge pull request #14 from praekeltfoundation/feature/add-static-segments-to-session
Add static segments to session if rules pass
2018-02-09 12:30:55 +02:00
824e42174f Tests 2018-02-08 19:48:31 +02:00
d114bb2570 Always add the segment to the session if they pass 2018-02-08 19:47:35 +02:00
7bba1e57cc Merge pull request #12 from praekeltfoundation/feature/only-deploy-once-per-build
Only deploy for one environment per travis build
2018-02-06 16:26:43 +02:00
3017f32b6b Add spaces around = in bash deploy condition 2018-02-06 16:18:16 +02:00
6b1a7cf1f2 Only deploy for one environment per travis build 2018-02-06 16:11:20 +02:00
1525b7946c Merge tag '0.10.6' into develop
Accepts and stores randomisation percentage for segment
Adds users to segment based on random number relative to percentage
2018-02-06 15:26:25 +02:00
7bf1bc3f19 Merge branch 'release/0.10.6' 2018-02-06 15:26:02 +02:00
4c60bcbe6b Version 0.10.6 2018-02-06 15:25:51 +02:00
ad4f75d471 Merge pull request #11 from praekeltfoundation/feature/SAS-93-use-randomisation-percentage
Use randomisation percentage
2018-02-06 14:51:56 +02:00
086168954d Test randomisation of static segments at creation 2018-02-05 12:30:12 +02:00
881090f2f9 Test randomisation for dynamic segments 2018-02-05 12:21:09 +02:00
d073c7d268 Randomise into static segments when they are created 2018-02-05 12:20:10 +02:00
7200b5b4c4 If a session passes segment rules randomise them into the segement 2018-02-05 12:19:36 +02:00
6f97c76958 Add method to randomise matching sessions into the segment 2018-02-05 12:18:22 +02:00
ecb4f928fb Merge pull request #10 from praekeltfoundation/feature/SAS-92-store-randomisation-percentage
Add randomisation percentage to segment model
2018-02-05 11:54:23 +02:00
29aa91477e Migrations 2018-02-02 10:15:20 +02:00
5c3acc6661 Display randomisation percentages on segment dashboard 2018-02-02 10:15:04 +02:00
602919d2d4 Test randomisation percentage added to segments 2018-02-02 10:14:18 +02:00
ae97118c3f Store randomisation percentage on segment model 2018-02-02 10:13:18 +02:00
51774b939e Version 0.10.5 2018-01-26 17:57:43 +02:00
908f85e295 Merge pull request #9 from praekeltfoundation/feature/SAS-86-display-record-counter-on-dash
Display record counter on segments in dash
2018-01-26 17:51:34 +02:00
99f9700ed0 Display record counter for active segments 2018-01-26 16:21:15 +02:00
7fa8ee1a46 Merge pull request #8 from praekeltfoundation/feature/SAS-85-only-count-active-frontend-users
Don't include staff and inactive users when counting matched users
2018-01-26 16:01:02 +02:00
5ad70d68f6 Don't include staff and inactive users when counting matched users 2018-01-26 15:38:26 +02:00
06bfe77901 Merge pull request #7 from praekeltfoundation/feature/SAS-85-calculate-matching-users
Count number of users that match static rules for a segment
2018-01-26 08:58:00 +02:00
d5e89d374b Remove unnecessary imports 2018-01-25 19:51:50 +02:00
5b39e82f80 Fixed test for adding user counter to segment 2018-01-25 18:42:38 +02:00
fbcebb43a4 Store record count on a segment when it is created 2018-01-25 15:14:19 +02:00
ef271587ec Test count_matching_users method 2018-01-25 13:26:05 +02:00
786a8801b1 Migrations for Segment.matched_user_count 2018-01-25 11:26:57 +02:00
caf73aa43c Add matched_users_count field to segments 2018-01-25 11:12:46 +02:00
4021d2c915 Add method to calculate the number of users that match a segment 2018-01-24 22:00:28 +02:00
33f96af4a3 Allow test_user() for static rules to accept a user 2018-01-24 15:14:24 +02:00
6299feb497 Merge tag '0.10.4' into develop
0.10.4
2018-01-22 12:58:49 +02:00
7ced6db126 Merge branch 'release/0.10.4' 2018-01-22 12:58:31 +02:00
c6ce67c9c9 Version bump 0.10.4 2018-01-22 12:58:12 +02:00
3df3fc0b16 Merge pull request #6 from praekeltfoundation/feature/SAS-87-fix-static-rules
Set QueryRule to be static
2018-01-22 12:03:29 +02:00
a00929846e Set query rule to be static 2018-01-18 16:17:30 +02:00
49fba11049 Merge pull request #5 from praekeltfoundation/enable-linting
Enable and fix lint
2018-01-08 10:23:36 +00:00
e3488e87ad Enable and fix lint 2018-01-08 09:08:11 +00:00
808aa6d202 Add tests for exclude_variants 2018-01-08 09:07:15 +00:00
efb060cc6e Merge branch 'release/0.10.3' into develop 2018-01-05 19:18:14 +02:00
28 changed files with 842 additions and 66 deletions

View File

@ -4,6 +4,9 @@ language: python
matrix:
include:
- python: 2.7
env: lint
- python: 2.7
env: TOXENV=py27-django111-wagtail113
@ -25,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

31
CHANGES
View File

@ -1,3 +1,34 @@
0.11.0
==================
- Bug Fix: Query rule should not be static
- Enable retrieval of user data for static rules through csv download
0.10.9
==================
- Bug Fix: Display the number of users in a static segment on dashboard
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
- Adds users to segment based on random number relative to percentage
0.10.5
==================
- Count how many users match a segments rules before saving the segment
- Stores count on the segment and displays in the dashboard
- Enables testing users against rules if there isn't an active request
0.10.0
==================
- Adds static and dynamic segments

View File

@ -55,10 +55,10 @@ author = 'Lab Digital BV'
# built documents.
#
# The short X.Y version.
version = '0.10.3'
version = '0.11.0'
# The full version, including alpha/beta/rc tags.
release = '0.10.3'
release = '0.11.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.10.3
current_version = 0.11.0
commit = true
tag = true
tag_name = {new_version}

View File

@ -32,7 +32,7 @@ with open('README.rst') as fh:
setup(
name='wagtail-personalisation-molo',
version='0.10.3',
version='0.11.0',
description='A forked version of Wagtail add-on for showing personalized content',
author='Praekelt.org',
author_email='dev@praekeltfoundation.org',

View File

@ -3,7 +3,6 @@ from __future__ import absolute_import, unicode_literals
from django.conf import settings
from django.db.models import F
from django.utils.module_loading import import_string
from django.utils import timezone
from wagtail_personalisation.models import Segment
from wagtail_personalisation.rules import AbstractBaseRule
@ -67,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 = (
@ -87,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 = []
@ -109,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.
@ -172,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:
@ -186,14 +197,19 @@ class SessionSegmentsAdapter(BaseSegmentsAdapter):
result = self._test_rules(segment_rules, self.request,
match_any=segment.match_any)
if result and segment.is_static and not segment.is_full:
if self.request.user.is_authenticated():
segment.static_users.add(self.request.user)
if result:
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:
excluded_segments += [segment]
self.set_segments(current_segments + additional_segments)
self.set_segments(excluded_segments, "excluded_segments")
self.update_visit_count()

View File

@ -13,4 +13,6 @@ urlpatterns = [
views.copy_page_view, name='copy_page'),
url(r'^segment/toggle_segment_view/$',
views.toggle_segment_view, name='toggle_segment_view'),
url(r'^segment/users/(?P<segment_id>[0-9]+)$',
views.segment_user_data, name='segment_user_data'),
]

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
@ -8,15 +9,11 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.sessions.models import Session
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.core.exceptions import ValidationError
from django.test.client import RequestFactory
from django.utils import timezone
from django.utils.lru_cache import lru_cache
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin.forms import WagtailAdminModelForm
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
@ -29,8 +26,30 @@ def user_from_data(user_id):
return AnonymousUser()
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.filter(is_active=True, is_staff=False)
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
@ -62,13 +81,22 @@ class SegmentAdminForm(WagtailAdminModelForm):
if field not in excluded:
form.add_error(field, _('Cannot update a static segment'))
def save(self, *args, **kwargs):
is_new = not self.instance.id
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:
@ -79,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,
@ -91,10 +120,13 @@ class SegmentAdminForm(WagtailAdminModelForm):
request.user = user
request.session = SessionStore(session_key=session.session_key)
passes = adapter._test_rules(instance.get_rules(), request, instance.match_any)
if passes:
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

View File

@ -2,8 +2,8 @@
# Generated by Django 1.11.1 on 2017-05-31 14:28
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):

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

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.8 on 2018-01-31 16:12
from __future__ import unicode_literals
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wagtail_personalisation', '0016_auto_20180125_0918'),
]
operations = [
migrations.AddField(
model_name='segment',
name='randomisation_percent',
field=models.PositiveSmallIntegerField(blank=True, default=None, help_text='If this number is set each user matching the rules will have this percentage chance of being placed in the segment.', null=True, validators=[django.core.validators.MaxValueValidator(100), django.core.validators.MinValueValidator(0)]),
),
]

View File

@ -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),
),
]

View File

@ -1,8 +1,9 @@
from __future__ import absolute_import, unicode_literals
import random
from django import forms
from django.conf import settings
from django.contrib.sessions.models import Session
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models, transaction
from django.template.defaultfilters import slugify
from django.utils.encoding import python_2_unicode_compatible
@ -11,11 +12,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from modelcluster.models import ClusterableModel
from wagtail.wagtailadmin.edit_handlers import (
FieldPanel,
FieldRowPanel,
InlinePanel,
MultiFieldPanel,
)
FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel)
from wagtail.wagtailcore.models import Page
from wagtail_personalisation.rules import AbstractBaseRule
@ -86,6 +83,25 @@ 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)
randomisation_percent = models.PositiveSmallIntegerField(
null=True, blank=True, default=None,
help_text=_(
"If this number is set each user matching the rules will "
"have this percentage chance of being placed in the segment."
), validators=[
MaxValueValidator(100),
MinValueValidator(0)
])
objects = SegmentQuerySet.as_manager()
@ -102,6 +118,7 @@ class Segment(ClusterableModel):
FieldPanel('match_any'),
FieldPanel('type', widget=forms.RadioSelect),
FieldPanel('count', classname='count_field'),
FieldPanel('randomisation_percent', classname='percent_field'),
], heading="Segment"),
MultiFieldPanel([
InlinePanel(
@ -125,7 +142,7 @@ class Segment(ClusterableModel):
@classmethod
def all_static(cls, rules):
return all(rule.static for rule in rules)
return all(rule.static for rule in rules)
@property
def all_rules_static(self):
@ -172,6 +189,19 @@ class Segment(ClusterableModel):
if save:
self.save()
def randomise_into_segment(self):
""" Returns True if randomisation_percent is not set or it generates
a random number less than the randomisation_percent
This is so there is some randomisation in which users are added to the
segment
"""
if self.randomisation_percent is None:
return True
if random.randint(1, 100) <= self.randomisation_percent:
return True
return False
class PersonalisablePageMetadata(ClusterableModel):
"""The personalisable page model. Allows creation of variants with linked

View File

@ -2,17 +2,23 @@ from __future__ import absolute_import, unicode_literals
import re
from datetime import datetime
from importlib import import_module
from django.apps import apps
from django.conf import settings
from django.contrib.sessions.models import Session
from django.db import models
from django.template.defaultfilters import slugify
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.test.client import RequestFactory
from modelcluster.fields import ParentalKey
from user_agents import parse
from wagtail.wagtailadmin.edit_handlers import (
FieldPanel, FieldRowPanel, PageChooserPanel)
SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
@python_2_unicode_compatible
class AbstractBaseRule(models.Model):
@ -220,13 +226,37 @@ class VisitCountRule(AbstractBaseRule):
class Meta:
verbose_name = _('Visit count Rule')
def test_user(self, request):
def _get_user_session(self, user):
sessions = Session.objects.iterator()
for session in sessions:
session_data = session.get_decoded()
if session_data.get('_auth_user_id') == str(user.id):
return SessionStore(session_key=session.session_key)
return SessionStore()
def test_user(self, request, user=None):
# Local import for cyclic import
from wagtail_personalisation.adapters import (
get_segment_adapter, SessionSegmentsAdapter, SEGMENT_ADAPTER_CLASS)
if user:
# Create a fake request so we can use the adapter
request = RequestFactory().get('/')
request.user = user
# If we're using the session adapter check for an active session
if SEGMENT_ADAPTER_CLASS == SessionSegmentsAdapter:
request.session = self._get_user_session(user)
else:
request.session = SessionStore()
elif not request:
# Return false if we don't have a user or a request
return False
operator = self.operator
segment_count = self.count
# Local import for cyclic import
from wagtail_personalisation.adapters import get_segment_adapter
adapter = get_segment_adapter(request)
visit_count = adapter.get_visit_count(self.counted_page)
@ -252,6 +282,28 @@ class VisitCountRule(AbstractBaseRule):
),
}
def get_column_header(self):
return "Visit count - %s" % self.counted_page
def get_user_info_string(self, user):
# Local import for cyclic import
from wagtail_personalisation.adapters import (
get_segment_adapter, SessionSegmentsAdapter, SEGMENT_ADAPTER_CLASS)
# Create a fake request so we can use the adapter
request = RequestFactory().get('/')
request.user = user
# If we're using the session adapter check for an active session
if SEGMENT_ADAPTER_CLASS == SessionSegmentsAdapter:
request.session = self._get_user_session(user)
else:
request.session = SessionStore()
adapter = get_segment_adapter(request)
visit_count = adapter.get_visit_count(self.counted_page)
return str(visit_count)
class QueryRule(AbstractBaseRule):
"""Query rule to segment users based on matching queries.

View File

@ -38,11 +38,11 @@
<li class="stat_card">
{% trans "This segment is Static" %}
<span class="icon icon-fa-user">
{{ segment.sessions.count|localize }}
{% if segment.sessions.count < segment.count %}
{{ segment.static_users.count|localize }}
{% if segment.static_users.count < segment.count %}
/ {{ segment.count }} {% trans "member" %}{{ segment.count|pluralize }}
{% else %}
{% trans "member" %}{{ segment.sessions.count|pluralize }}
{% trans "member" %}{{ segment.count|pluralize }}
{% endif %}
</span>
</li>
@ -70,6 +70,13 @@
{% endif %}
</li>
{% if segment.randomisation_percent is not None %}
<li class="stat_card">
<span>{{ segment.randomisation_percent }} %</span>
{% trans "Chance that visitors matching the rules are added to the segment" %}
</li>
{% endif %}
{% for rule in segment.get_rules %}
<li class="stat_card {{ rule.encoded_name }}">
{{ rule.description.title }}
@ -80,6 +87,11 @@
{% endif %}
</li>
{% endfor %}
{% if segment.matched_users_count > 0 %}
<li class="stat_card">
<span class="icon icon-fa-user"> {{ segment.matched_users_count }} {% trans "user" %}{{ segment.matched_users_count|pluralize }}</span> {% trans "were possible matches for this segment at creation" %}
</li>
{% endif %}
</ul>
</div>
@ -91,6 +103,9 @@
<li><a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Disable this segment" %}">disable</a></li>
{% endif %}
<li><a href="{% url 'wagtail_personalisation_segment_modeladmin_edit' segment.pk %}" title="{% trans "Configure this segment" %}">configure this</a></li>
{% if segment.is_static %}
<li><a href="{% url 'segment:segment_user_data' segment.pk %}" title="{% trans "Download user info" %}">download users csv</a></li>
{% endif %}
</ul>
{% endif %}
</div>

View File

@ -103,9 +103,17 @@ def exclude_variants(pages):
:return: List of pages that aren't variants
:rtype: list
"""
return [page for page in pages
if (hasattr(page, 'personalisation_metadata') is False)
or (hasattr(page, 'personalisation_metadata')
and page.personalisation_metadata is None)
or (hasattr(page, 'personalisation_metadata')
and page.personalisation_metadata.is_canonical)]
return [
page for page in pages
if (
(
hasattr(page, 'personalisation_metadata') is False
) or
(
hasattr(page, 'personalisation_metadata') and page.personalisation_metadata is None
) or
(
hasattr(page, 'personalisation_metadata') and page.personalisation_metadata.is_canonical
)
)
]

View File

@ -1,8 +1,9 @@
from __future__ import absolute_import, unicode_literals
import csv
from django import forms
from django.core.urlresolvers import reverse
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.http import HttpResponse, HttpResponseForbidden, HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register
@ -139,3 +140,32 @@ def copy_page_view(request, page_id, segment_id):
return HttpResponseRedirect(edit_url)
return HttpResponseForbidden()
# CSV download views
def segment_user_data(request, segment_id):
if request.user.has_perm('wagtailadmin.access_admin'):
segment = get_object_or_404(Segment, pk=segment_id)
response = HttpResponse(content_type='text/csv; charset=utf-8')
response['Content-Disposition'] = \
'attachment;filename=segment-%s-users.csv' % str(segment_id)
headers = ['Username']
for rule in segment.get_rules():
if rule.static:
headers.append(rule.get_column_header())
writer = csv.writer(response)
writer.writerow(headers)
for user in segment.static_users.all():
row = [user.username]
for rule in segment.get_rules():
if rule.static:
row.append(rule.get_user_info_string(user))
writer.writerow(row)
return response
return HttpResponseForbidden()

View File

@ -7,7 +7,7 @@ from django.core.urlresolvers import reverse
from django.template.defaultfilters import pluralize
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin.site_summary import SummaryItem, PagesSummaryItem
from wagtail.wagtailadmin.site_summary import PagesSummaryItem, SummaryItem
from wagtail.wagtailadmin.widgets import Button, ButtonWithDropdownFromHook
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.models import Page

View File

@ -1,10 +1,9 @@
from __future__ import absolute_import, unicode_literals
import os
from pkg_resources import parse_version as V
import django
from pkg_resources import parse_version as V
DATABASES = {
'default': {
@ -56,6 +55,7 @@ TEMPLATES = [
},
]
def get_middleware_settings():
return (
'django.middleware.common.CommonMiddleware',
@ -69,6 +69,7 @@ def get_middleware_settings():
'wagtail.wagtailcore.middleware.SiteMiddleware',
)
# Django 1.10 started to use "MIDDLEWARE" instead of "MIDDLEWARE_CLASSES".
if V(django.get_version()) < V('1.10'):
MIDDLEWARE_CLASSES = get_middleware_settings()

View File

@ -21,7 +21,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='ContentPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), # noqa: E501
('subtitle', models.CharField(blank=True, default='', max_length=255)),
('body', wagtail.wagtailcore.fields.RichTextField(blank=True, default='')),
],

View File

@ -2,9 +2,9 @@
# Generated by Django 1.11.1 on 2017-06-02 04:26
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import wagtail.wagtailcore.fields
from django.db import migrations, models
class Migration(migrations.Migration):
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='RegularPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')), # noqa: E501
('subtitle', models.CharField(blank=True, default='', max_length=255)),
('body', wagtail.wagtailcore.fields.RichTextField(blank=True, default='')),
],

View File

@ -4,16 +4,14 @@ import datetime
import pytest
from tests.factories.page import ContentPageFactory
from tests.factories.rule import (
DayRuleFactory, DeviceRuleFactory, ReferralRuleFactory, TimeRuleFactory)
from tests.factories.rule import ReferralRuleFactory, QueryRuleFactory
from tests.factories.segment import SegmentFactory
from tests.factories.site import SiteFactory
from wagtail_personalisation.models import Segment
from wagtail_personalisation.rules import TimeRule
# Factory tests
@pytest.mark.django_db
def test_segment_create():
factoried_segment = SegmentFactory()
@ -27,8 +25,6 @@ def test_segment_create():
assert factoried_segment.status == segment.status
@pytest.mark.django_db
def test_referral_rule_create():
segment = SegmentFactory(name='Referral')
@ -37,3 +33,15 @@ def test_referral_rule_create():
segment=segment)
assert referral_rule.regex_string == 'test.test'
@pytest.mark.django_db
def test_query_rule_create():
segment = SegmentFactory(name='Query')
query_rule = QueryRuleFactory(
parameter="query",
value="value",
segment=segment)
assert query_rule.parameter == 'query'
assert query_rule.value == 'value'

View File

@ -16,6 +16,8 @@ def test_time_rule_create():
segment=segment)
assert time_rule.start_time == datetime.time(8, 0, 0)
@pytest.mark.django_db
@freeze_time("10:00:00")
def test_requesttime_segment(client, site):

View File

@ -1,5 +1,8 @@
import pytest
from tests.factories.rule import VisitCountRuleFactory
from tests.factories.segment import SegmentFactory
@pytest.mark.django_db
def test_visit_count(site, client):
@ -20,3 +23,50 @@ def test_visit_count(site, client):
visit_count = client.session['visit_count']
assert visit_count[0]['count'] == 2
assert visit_count[1]['count'] == 1
@pytest.mark.django_db
def test_visit_count_call_test_user_with_user(site, client, user):
segment = SegmentFactory(name='VisitCount')
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
session = client.session
session['visit_count'] = [{'path': '/', 'count': 2}]
session.save()
client.force_login(user)
assert rule.test_user(None, user)
@pytest.mark.django_db
def test_visit_count_call_test_user_with_user_or_request_fails(site, client, user):
segment = SegmentFactory(name='VisitCount')
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
session = client.session
session['visit_count'] = [{'path': '/', 'count': 2}]
session.save()
client.force_login(user)
assert not rule.test_user(None)
@pytest.mark.django_db
def test_get_column_header(site):
segment = SegmentFactory(name='VisitCount')
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
assert rule.get_column_header() == 'Visit count - Test page'
@pytest.mark.django_db
def test_get_user_info_string_returns_count(site, client, user):
segment = SegmentFactory(name='VisitCount')
rule = VisitCountRuleFactory(counted_page=site.root_page, segment=segment)
session = client.session
session['visit_count'] = [{'path': '/', 'count': 2}]
session.save()
client.force_login(user)
assert rule.get_user_info_string(user) == '2'

View File

@ -2,18 +2,17 @@ from __future__ import absolute_import, unicode_literals
import datetime
from django.forms.models import model_to_dict
from django.contrib.sessions.backends.db import SessionStore
import pytest
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 tests.factories.segment import SegmentFactory
def form_with_data(segment, *rules):
model_fields = ['type', 'status', 'count', 'name', 'match_any']
model_fields = ['type', 'status', 'count', 'name', 'match_any', 'randomisation_percent']
class TestSegmentAdminForm(SegmentAdminForm):
class Meta:
@ -63,6 +62,7 @@ def test_anonymous_user_not_added_to_static_segment_at_creation(site, client):
assert not instance.static_users.all()
@pytest.mark.django_db
def test_match_any_correct_populates(site, client, django_user_model):
user = django_user_model.objects.create(username='first')
@ -246,3 +246,338 @@ 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_randomisation_percentage_added_to_segment_at_creation(site, client, mocker, django_user_model):
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
segment.randomisation_percent = 80
rule = VisitCountRule()
form = form_with_data(segment, rule)
instance = form.save()
assert instance.randomisation_percent == 80
@pytest.mark.django_db
def test_randomisation_percentage_min_zero(site, client, mocker, django_user_model):
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
segment.randomisation_percent = -1
rule = VisitCountRule()
form = form_with_data(segment, rule)
assert not form.is_valid()
@pytest.mark.django_db
def test_randomisation_percentage_max_100(site, client, mocker, django_user_model):
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
segment.randomisation_percent = 101
rule = VisitCountRule()
form = form_with_data(segment, rule)
assert not form.is_valid()
@pytest.mark.django_db
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)
form = form_with_data(segment, rule)
instance = form.save()
mocker.patch('random.randint', return_value=39)
session = client.session
session.save()
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_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)
form = form_with_data(segment, rule)
instance = form.save()
mocker.patch('random.randint', return_value=41)
session = client.session
session.save()
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
def test_not_in_segment_if_percentage_is_0(site, client, mocker, user):
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, count=1,
randomisation_percent=0)
rule = VisitCountRule(counted_page=site.root_page)
form = form_with_data(segment, rule)
instance = form.save()
session = client.session
session.save()
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_always_in_segment_if_percentage_is_100(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()
session = client.session
session.save()
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_added_to_static_segment_at_creation_if_random_above_percent(site, client, mocker, user):
session = client.session
session.save()
client.force_login(user)
client.get(site.root_page.url)
mocker.patch('random.randint', return_value=41)
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, randomisation_percent=40)
rule = VisitCountRule(counted_page=site.root_page)
form = form_with_data(segment, rule)
instance = form.save()
assert user not in instance.static_users.all()
assert user in instance.excluded_users.all()
@pytest.mark.django_db
def test_added_to_static_segment_at_creation_if_random_below_percent(site, client, mocker, user):
session = client.session
session.save()
client.force_login(user)
client.get(site.root_page.url)
mocker.patch('random.randint', return_value=39)
segment = SegmentFactory.build(type=Segment.TYPE_STATIC, randomisation_percent=40)
rule = VisitCountRule(counted_page=site.root_page)
form = form_with_data(segment, rule)
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
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, 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(counted_page=site.root_page)
form = form_with_data(segment, rule)
mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
assert form.count_matching_users([rule], True) is 2
@pytest.mark.django_db
def test_count_matching_users_excludes_staff(site, client, mocker, django_user_model):
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second', is_staff=True)
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
rule = VisitCountRule(counted_page=site.root_page)
form = form_with_data(segment, rule)
mock_test_user = mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
assert form.count_matching_users([rule], True) is 1
assert mock_test_user.call_count == 1
@pytest.mark.django_db
def test_count_matching_users_excludes_inactive(site, client, mocker, django_user_model):
django_user_model.objects.create(username='first')
django_user_model.objects.create(username='second', is_active=False)
segment = SegmentFactory.build(type=Segment.TYPE_STATIC)
rule = VisitCountRule(counted_page=site.root_page)
form = form_with_data(segment, rule)
mock_test_user = mocker.patch('wagtail_personalisation.rules.VisitCountRule.test_user', return_value=True)
assert form.count_matching_users([rule], True) is 1
assert mock_test_user.call_count == 1
@pytest.mark.django_db
def test_count_matching_users_only_counts_static_rules(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 = TimeRule(
start_time=datetime.time(0, 0, 0),
end_time=datetime.time(23, 59, 59),
segment=segment,
)
form = form_with_data(segment, rule)
mock_test_user = mocker.patch('wagtail_personalisation.rules.TimeRule.test_user')
assert form.count_matching_users([rule], True) is 0
assert mock_test_user.call_count == 0
@pytest.mark.django_db
def test_count_matching_users_handles_match_any(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)
first_rule = VisitCountRule(counted_page=site.root_page)
other_page = site.root_page.get_last_child()
second_rule = VisitCountRule(counted_page=other_page)
form = form_with_data(segment, first_rule, second_rule)
mock_test_user = mocker.patch(
'wagtail_personalisation.rules.VisitCountRule.test_user',
side_effect=[True, False, True, False])
assert form.count_matching_users([first_rule, second_rule], True) is 2
mock_test_user.call_count == 4
@pytest.mark.django_db
def test_count_matching_users_handles_match_all(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)
first_rule = VisitCountRule(counted_page=site.root_page)
other_page = site.root_page.get_last_child()
second_rule = VisitCountRule(counted_page=other_page)
form = form_with_data(segment, first_rule, second_rule)
mock_test_user = mocker.patch(
'wagtail_personalisation.rules.VisitCountRule.test_user',
side_effect=[True, True, False, True])
assert form.count_matching_users([first_rule, second_rule], False) is 1
mock_test_user.call_count == 4

View File

@ -1,4 +1,5 @@
from wagtail_personalisation.utils import impersonate_other_page
from wagtail_personalisation.utils import (
exclude_variants, impersonate_other_page)
class Page(object):
@ -19,3 +20,40 @@ def test_impersonate_other_page():
impersonate_other_page(page, other_page)
assert page == other_page
class Metadata(object):
def __init__(self, is_canonical=True):
self.is_canonical = is_canonical
class PersonalisationMetadataPage(object):
def __init__(self):
self.personalisation_metadata = Metadata()
def test_exclude_variants_includes_pages_with_no_metadata_property():
page = PersonalisationMetadataPage()
del page.personalisation_metadata
result = exclude_variants([page])
assert result == [page]
def test_exclude_variants_includes_pages_with_metadata_none():
page = PersonalisationMetadataPage()
page.personalisation_metadata = None
result = exclude_variants([page])
assert result == [page]
def test_exclude_variants_includes_pages_with_metadata_canonical():
page = PersonalisationMetadataPage()
result = exclude_variants([page])
assert result == [page]
def test_exclude_variants_excludes_pages_with_metadata_not_canonical():
page = PersonalisationMetadataPage()
page.personalisation_metadata.is_canonical = False
result = exclude_variants([page])
assert result == []

55
tests/unit/test_views.py Normal file
View File

@ -0,0 +1,55 @@
import pytest
from django.contrib.auth.models import Permission
from django.core.urlresolvers import reverse
from wagtail_personalisation.models import Segment
from wagtail_personalisation.rules import VisitCountRule
@pytest.mark.django_db
def test_segment_user_data_view_requires_admin_access(site, client, django_user_model):
user = django_user_model.objects.create(username='first')
segment = Segment(type=Segment.TYPE_STATIC, count=1)
segment.save()
client.force_login(user)
url = reverse('segment:segment_user_data', args=(segment.id,))
response = client.get(url)
assert response.status_code == 302
assert response.url == '/admin/login/?next=%s' % url
@pytest.mark.django_db
def test_segment_user_data_view(site, client, mocker, django_user_model):
user1 = django_user_model.objects.create(username='first')
user2 = django_user_model.objects.create(username='second')
admin_user = django_user_model.objects.create(username='admin')
permission = Permission.objects.get(codename='access_admin')
admin_user.user_permissions.add(permission)
segment = Segment(type=Segment.TYPE_STATIC, count=1)
segment.save()
segment.static_users.add(user1)
segment.static_users.add(user2)
rule1 = VisitCountRule(counted_page=site.root_page, segment=segment)
rule2 = VisitCountRule(counted_page=site.root_page.get_last_child(),
segment=segment)
rule1.save()
rule2.save()
mocker.patch('wagtail_personalisation.rules.VisitCountRule.get_user_info_string',
side_effect=[3, 9, 0, 1])
client.force_login(admin_user)
response = client.get(
reverse('segment:segment_user_data', args=(segment.id,)))
assert response.status_code == 200
data_lines = response.content.decode().split("\n")
assert data_lines[0] == 'Username,Visit count - Test page,Visit count - Regular page\r'
assert data_lines[1] == 'first,3,9\r'
assert data_lines[2] == 'second,0,1\r'

View File

@ -20,7 +20,7 @@ commands =
[testenv:lint]
basepython = python2.7
deps = flake8
deps = flake8==3.5.0
commands =
flake8 src tests setup.py
isort -q --recursive --diff src/ tests/