From e7accad5759b563a7aeed044ccff7a4974b5d56b Mon Sep 17 00:00:00 2001 From: Boris Besemer Date: Mon, 7 Nov 2016 15:22:22 +0100 Subject: [PATCH 1/5] adds base and time rules --- src/personalisation/admin.py | 17 +++++++ src/personalisation/middleware.py | 11 ++++- .../0002_abstractrulebase_timerule.py | 32 +++++++++++++ .../0003_abstractrulebase_segment.py | 22 +++++++++ src/personalisation/models.py | 48 ++++++++++++++++++- 5 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 src/personalisation/admin.py create mode 100644 src/personalisation/migrations/0002_abstractrulebase_timerule.py create mode 100644 src/personalisation/migrations/0003_abstractrulebase_segment.py diff --git a/src/personalisation/admin.py b/src/personalisation/admin.py new file mode 100644 index 0000000..c111dea --- /dev/null +++ b/src/personalisation/admin.py @@ -0,0 +1,17 @@ +from django.contrib import admin + +from personalisation import models + +class TimeRuleAdmin(admin.ModelAdmin): + list_display = ('name', 'start_time', 'end_time') + +class TimeRuleAdminInline(admin.TabularInline): + model = models.TimeRule + +class SegmentAdmin(admin.ModelAdmin): + list_display = ['name'] + inlines = (TimeRuleAdminInline,) + + +admin.site.register(models.TimeRule, TimeRuleAdmin) +admin.site.register(models.Segment, SegmentAdmin) diff --git a/src/personalisation/middleware.py b/src/personalisation/middleware.py index 0178f91..be2fb1b 100644 --- a/src/personalisation/middleware.py +++ b/src/personalisation/middleware.py @@ -1,3 +1,5 @@ +from personalisation.models import TimeRule + class SegmentMiddleware(object): """Middleware for testing and putting a user in a segment""" @@ -5,7 +7,14 @@ class SegmentMiddleware(object): self.get_response = get_response def __call__(self, request): - request.session['segmented'] = True + time_rules = TimeRule.objects.all() + + result = False + + for rule in time_rules: + result = rule.test_user() + + request.session['segmented'] = result response = self.get_response(request) print(request.session['segmented']) diff --git a/src/personalisation/migrations/0002_abstractrulebase_timerule.py b/src/personalisation/migrations/0002_abstractrulebase_timerule.py new file mode 100644 index 0000000..93f34f5 --- /dev/null +++ b/src/personalisation/migrations/0002_abstractrulebase_timerule.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-11-07 13:53 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('personalisation', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='AbstractRuleBase', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='TimeRule', + fields=[ + ('abstractrulebase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='personalisation.AbstractRuleBase')), + ('start_time', models.TimeField(verbose_name='Starting time')), + ('end_time', models.TimeField(verbose_name='Ending time')), + ], + bases=('personalisation.abstractrulebase',), + ), + ] diff --git a/src/personalisation/migrations/0003_abstractrulebase_segment.py b/src/personalisation/migrations/0003_abstractrulebase_segment.py new file mode 100644 index 0000000..c31b332 --- /dev/null +++ b/src/personalisation/migrations/0003_abstractrulebase_segment.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-11-07 14:12 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('personalisation', '0002_abstractrulebase_timerule'), + ] + + operations = [ + migrations.AddField( + model_name='abstractrulebase', + name='segment', + field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='segment', to='personalisation.Segment'), + preserve_default=False, + ), + ] diff --git a/src/personalisation/models.py b/src/personalisation/models.py index 14399df..a575188 100644 --- a/src/personalisation/models.py +++ b/src/personalisation/models.py @@ -1,8 +1,11 @@ from __future__ import absolute_import, unicode_literals +from datetime import datetime, time + from django.conf import settings from django.db import models from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ from modelcluster.models import ClusterableModel from wagtail.wagtailadmin.edit_handlers import FieldPanel @@ -20,4 +23,47 @@ class Segment(ClusterableModel): ] def __str__(self): - return self.name \ No newline at end of file + return self.name + + +""" +Base for creating rules to segment users with +""" +@python_2_unicode_compatible +class AbstractRuleBase(models.Model): + name = models.CharField(max_length=255) + segment = models.ForeignKey(to=Segment, related_name="segment") + + def test_user(self, request=None): + return True + + def return_segment_id(self): + return "".join(self.name.lower().split()) + + def __str__(self): + return self.name + + +""" +Time rule to segment users with +""" +@python_2_unicode_compatible +class TimeRule(AbstractRuleBase): + start_time = models.TimeField(_("Starting time")) + end_time = models.TimeField(_("Ending time")) + + def __init__(self, *args, **kwargs): + super(TimeRule, self).__init__(*args, **kwargs) + + def test_user(self, request=None): + current_time = datetime.now().time() + starting_time = self.start_time + ending_time = self.end_time + + if starting_time <= current_time <= ending_time: + return True + else: + return False + + + From eac54cda509b0f1a2cdb9bf72bdf98445429a2ab Mon Sep 17 00:00:00 2001 From: Boris Besemer Date: Mon, 7 Nov 2016 15:42:03 +0100 Subject: [PATCH 2/5] now adds segments of a user to the session --- src/personalisation/middleware.py | 19 +++++++++++------- .../migrations/0004_segment_status.py | 20 +++++++++++++++++++ src/personalisation/models.py | 10 ++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 src/personalisation/migrations/0004_segment_status.py diff --git a/src/personalisation/middleware.py b/src/personalisation/middleware.py index be2fb1b..5709923 100644 --- a/src/personalisation/middleware.py +++ b/src/personalisation/middleware.py @@ -1,4 +1,4 @@ -from personalisation.models import TimeRule +from personalisation.models import Segment, TimeRule class SegmentMiddleware(object): """Middleware for testing and putting a user in a segment""" @@ -7,16 +7,21 @@ class SegmentMiddleware(object): self.get_response = get_response def __call__(self, request): - time_rules = TimeRule.objects.all() + segments = Segment.objects.all().filter(status="live") - result = False + chosen_segments = [] - for rule in time_rules: - result = rule.test_user() + for segment in segments: + result = False + rules = TimeRule.objects.all().filter(segment=segment) + for rule in rules: + result = rule.test_user() + if result: + chosen_segments.append(segment.encoded_name()) - request.session['segmented'] = result + request.session['segments'] = chosen_segments response = self.get_response(request) - print(request.session['segmented']) + print(request.session['segments']) return response diff --git a/src/personalisation/migrations/0004_segment_status.py b/src/personalisation/migrations/0004_segment_status.py new file mode 100644 index 0000000..4f18027 --- /dev/null +++ b/src/personalisation/migrations/0004_segment_status.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.3 on 2016-11-07 14:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('personalisation', '0003_abstractrulebase_segment'), + ] + + operations = [ + migrations.AddField( + model_name='segment', + name='status', + field=models.CharField(choices=[('disabled', 'Disabled'), ('live', 'Live'), ('completed', 'Completed')], default='disabled', max_length=20), + ), + ] diff --git a/src/personalisation/models.py b/src/personalisation/models.py index a575188..e71f34a 100644 --- a/src/personalisation/models.py +++ b/src/personalisation/models.py @@ -17,6 +17,12 @@ Model for a new segment @python_2_unicode_compatible class Segment(ClusterableModel): name = models.CharField(max_length=255) + STATUS_CHOICES = ( + ('disabled', 'Disabled'), + ('live', 'Live'), + ('completed', 'Completed'), + ) + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="disabled") panels = [ FieldPanel('name'), @@ -25,6 +31,10 @@ class Segment(ClusterableModel): def __str__(self): return self.name + def encoded_name(self): + return "".join(self.name.lower().split()) + + """ Base for creating rules to segment users with From 59aec0871eab20c9f73e51e2dc2f8c03f1b8fc65 Mon Sep 17 00:00:00 2001 From: Boris Besemer Date: Mon, 7 Nov 2016 15:56:44 +0100 Subject: [PATCH 3/5] adds updated readme with instructions --- README.rst | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9fc0b80..32fa1df 100644 --- a/README.rst +++ b/README.rst @@ -1,4 +1,33 @@ Wagtail personalisation module ============================== -WIP \ No newline at end of file +Personalisation module for Wagtail. This project is still work in progresss. + + +Instructions +------------ +To install the package with pip:: + + pip install wagtail-personalisation + +Next, include the ``personalisation`` app in your project's ``INSTALLED_APPS``: + +.. code-block:: python + + INSTALLED_APPS = [ + # ... + 'personalisation', + # ... + ] + +Last but not least, add ``personalisation.middleware.SegmentMiddleware`` to your project's ``MIDDLEWARE``: + +.. code-block:: python + + MIDDLEWARE = [ + # ... + 'personalisation.middleware.SegmentMiddleware', + # ... + ] + +Make sure that ``django.contrib.sessions.middleware.SessionMiddleware`` has been added in first, this is a prerequisite for this project. From 35a8bd5c8e68d45fc4dc4362a464f6fd03038d5c Mon Sep 17 00:00:00 2001 From: Boris Besemer Date: Mon, 7 Nov 2016 17:11:15 +0100 Subject: [PATCH 4/5] adds automatic rule subclass checks in the middleware --- setup.py | 3 ++- src/personalisation/middleware.py | 4 ++-- ...merule.py => 0002_abstractbaserule_timerule.py} | 6 +++--- ...segment.py => 0003_abstractbaserule_segment.py} | 4 ++-- .../migrations/0004_segment_status.py | 2 +- src/personalisation/models.py | 14 +++++--------- 6 files changed, 15 insertions(+), 18 deletions(-) rename src/personalisation/migrations/{0002_abstractrulebase_timerule.py => 0002_abstractbaserule_timerule.py} (83%) rename src/personalisation/migrations/{0003_abstractrulebase_segment.py => 0003_abstractbaserule_segment.py} (83%) diff --git a/setup.py b/setup.py index d6144f3..5df0615 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ setup( 'Topic :: Internet :: WWW/HTTP :: Site Management', ], install_requires=[ + 'django-model-utils==2.6', 'wagtail>=1.7', ] -) \ No newline at end of file +) diff --git a/src/personalisation/middleware.py b/src/personalisation/middleware.py index 5709923..beb0290 100644 --- a/src/personalisation/middleware.py +++ b/src/personalisation/middleware.py @@ -1,4 +1,4 @@ -from personalisation.models import Segment, TimeRule +from personalisation.models import Segment, AbstractBaseRule, TimeRule class SegmentMiddleware(object): """Middleware for testing and putting a user in a segment""" @@ -13,7 +13,7 @@ class SegmentMiddleware(object): for segment in segments: result = False - rules = TimeRule.objects.all().filter(segment=segment) + rules = AbstractBaseRule.objects.filter(segment=segment).select_subclasses() for rule in rules: result = rule.test_user() if result: diff --git a/src/personalisation/migrations/0002_abstractrulebase_timerule.py b/src/personalisation/migrations/0002_abstractbaserule_timerule.py similarity index 83% rename from src/personalisation/migrations/0002_abstractrulebase_timerule.py rename to src/personalisation/migrations/0002_abstractbaserule_timerule.py index 93f34f5..2cae5d2 100644 --- a/src/personalisation/migrations/0002_abstractrulebase_timerule.py +++ b/src/personalisation/migrations/0002_abstractbaserule_timerule.py @@ -14,7 +14,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='AbstractRuleBase', + name='AbstractBaseRule', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=255)), @@ -23,10 +23,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='TimeRule', fields=[ - ('abstractrulebase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='personalisation.AbstractRuleBase')), + ('abstractbaserule_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='personalisation.AbstractBaseRule')), ('start_time', models.TimeField(verbose_name='Starting time')), ('end_time', models.TimeField(verbose_name='Ending time')), ], - bases=('personalisation.abstractrulebase',), + bases=('personalisation.abstractbaserule',), ), ] diff --git a/src/personalisation/migrations/0003_abstractrulebase_segment.py b/src/personalisation/migrations/0003_abstractbaserule_segment.py similarity index 83% rename from src/personalisation/migrations/0003_abstractrulebase_segment.py rename to src/personalisation/migrations/0003_abstractbaserule_segment.py index c31b332..d2ac8b4 100644 --- a/src/personalisation/migrations/0003_abstractrulebase_segment.py +++ b/src/personalisation/migrations/0003_abstractbaserule_segment.py @@ -9,12 +9,12 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('personalisation', '0002_abstractrulebase_timerule'), + ('personalisation', '0002_abstractbaserule_timerule'), ] operations = [ migrations.AddField( - model_name='abstractrulebase', + model_name='abstractbaserule', name='segment', field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='segment', to='personalisation.Segment'), preserve_default=False, diff --git a/src/personalisation/migrations/0004_segment_status.py b/src/personalisation/migrations/0004_segment_status.py index 4f18027..76ad33b 100644 --- a/src/personalisation/migrations/0004_segment_status.py +++ b/src/personalisation/migrations/0004_segment_status.py @@ -8,7 +8,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('personalisation', '0003_abstractrulebase_segment'), + ('personalisation', '0003_abstractbaserule_segment'), ] operations = [ diff --git a/src/personalisation/models.py b/src/personalisation/models.py index e71f34a..2ac3b00 100644 --- a/src/personalisation/models.py +++ b/src/personalisation/models.py @@ -8,6 +8,7 @@ from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ from modelcluster.models import ClusterableModel +from model_utils.managers import InheritanceManager from wagtail.wagtailadmin.edit_handlers import FieldPanel @@ -40,9 +41,10 @@ class Segment(ClusterableModel): Base for creating rules to segment users with """ @python_2_unicode_compatible -class AbstractRuleBase(models.Model): +class AbstractBaseRule(models.Model): name = models.CharField(max_length=255) segment = models.ForeignKey(to=Segment, related_name="segment") + objects = InheritanceManager() def test_user(self, request=None): return True @@ -58,7 +60,7 @@ class AbstractRuleBase(models.Model): Time rule to segment users with """ @python_2_unicode_compatible -class TimeRule(AbstractRuleBase): +class TimeRule(AbstractBaseRule): start_time = models.TimeField(_("Starting time")) end_time = models.TimeField(_("Ending time")) @@ -70,10 +72,4 @@ class TimeRule(AbstractRuleBase): starting_time = self.start_time ending_time = self.end_time - if starting_time <= current_time <= ending_time: - return True - else: - return False - - - + return starting_time <= current_time <= ending_time From 72aad87f01b9edeeed6a43c2502a55ef7fc7a1d7 Mon Sep 17 00:00:00 2001 From: Jasper Berghoef Date: Tue, 8 Nov 2016 09:51:36 +0100 Subject: [PATCH 5/5] Update to readme Included modeladmin --- README.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 32fa1df..64b3f61 100644 --- a/README.rst +++ b/README.rst @@ -10,12 +10,13 @@ To install the package with pip:: pip install wagtail-personalisation -Next, include the ``personalisation`` app in your project's ``INSTALLED_APPS``: +Next, include the ``personalisation`` and ``wagtail.contrib.modeladmin`` app in your project's ``INSTALLED_APPS``: .. code-block:: python INSTALLED_APPS = [ # ... + 'wagtail.contrib.modeladmin', 'personalisation', # ... ] @@ -30,4 +31,4 @@ Last but not least, add ``personalisation.middleware.SegmentMiddleware`` to your # ... ] -Make sure that ``django.contrib.sessions.middleware.SessionMiddleware`` has been added in first, this is a prerequisite for this project. +Make sure that ``django.contrib.sessions.middleware.SessionMiddleware`` has been added in first, this is a prerequisite for this project. \ No newline at end of file