7

Compare commits

..

9 Commits

16 changed files with 81 additions and 252 deletions

View File

@ -25,7 +25,7 @@
"enable_date": "2017-06-02T10:58:39.389Z",
"disable_date": "2017-06-02T10:34:51.722Z",
"visit_count": 0,
"status": "enabled",
"enabled": true,
"persistent": false,
"match_any": false
}
@ -39,7 +39,7 @@
"enable_date": "2017-06-02T10:57:44.497Z",
"disable_date": "2017-06-02T10:57:39.984Z",
"visit_count": 1,
"status": "enabled",
"enabled": true,
"persistent": false,
"match_any": false
}

View File

@ -12,7 +12,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('wagtailcore', '0001_initial'),
('wagtailcore', '0033_remove_golive_expiry_help_text'),
('wagtail_personalisation', '0011_personalisablepagemetadata'),
]

View File

@ -1,32 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-31 16:15
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import wagtail.wagtailcore.blocks
import wagtail.wagtailcore.fields
import wagtail.wagtailimages.blocks
import wagtail_personalisation.blocks
class Migration(migrations.Migration):
dependencies = [
('wagtailcore', '0001_initial'),
('home', '0003_homepage_text_content'),
]
operations = [
migrations.CreateModel(
name='PersonalisedFieldsPage',
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')),
('body', wagtail.wagtailcore.fields.StreamField((('personalised_block', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('heading', wagtail.wagtailcore.blocks.CharBlock()), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock())))), ('personalised_block_template', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('heading', wagtail.wagtailcore.blocks.CharBlock()), ('paragraph', wagtail.wagtailcore.blocks.RichTextBlock())), label='Block with template', template='blocks/personalised_block_template.html')), ('personalised_rich_text_block', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('rich_text', wagtail.wagtailcore.blocks.RichTextBlock(label='Rich Text'))))), ('personalised_image', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('image', wagtail.wagtailimages.blocks.ImageChooserBlock(label='Image'))))), ('personalised_char', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('char', wagtail.wagtailcore.blocks.CharBlock(label='Text'))))), ('personalised_text', wagtail.wagtailcore.blocks.StructBlock((('segment', wagtail.wagtailcore.blocks.ChoiceBlock(choices=wagtail_personalisation.blocks.list_segment_choices, help_text='Only show this content block for users in this segment', label='Personalisation segment', required=False)), ('text', wagtail.wagtailcore.blocks.TextBlock(label='Mutli-line Text')))))))),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]

View File

@ -5,11 +5,8 @@ from wagtail.core import blocks
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.models import Page
from wagtail_personalisation.blocks import (
PersonalisedCharBlock, PersonalisedImageChooserBlock,
PersonalisedRichTextBlock, PersonalisedStructBlock,
PersonalisedTextBlock)
from wagtail_personalisation.models import PersonalisablePageMixin
from wagtail_personalisation.blocks import PersonalisedStructBlock
class HomePage(PersonalisablePageMixin, Page):
@ -24,24 +21,3 @@ class HomePage(PersonalisablePageMixin, Page):
RichTextFieldPanel('intro'),
StreamFieldPanel('body'),
]
class PersonalisedFieldsPage(Page):
body = StreamField([
('personalised_block', PersonalisedStructBlock([
('heading', blocks.CharBlock()),
('paragraph', blocks.RichTextBlock())
], render_fields=['heading', 'paragraph'])),
('personalised_block_template', PersonalisedStructBlock([
('heading', blocks.CharBlock()),
('paragraph', blocks.RichTextBlock())
], template='blocks/personalised_block_template.html', label=_('Block with template'))),
('personalised_rich_text_block', PersonalisedRichTextBlock()),
('personalised_image', PersonalisedImageChooserBlock()),
('personalised_char', PersonalisedCharBlock()),
('personalised_text', PersonalisedTextBlock()),
])
content_panels = Page.content_panels + [
StreamFieldPanel('body')
]

View File

@ -1,9 +0,0 @@
{% load wagtailcore_tags %}
<div class="personalisation-block-template" style="background-color: cornsilk;">
<p>This is a block with <strong>template</strong>.</p>
<h2>Heading: {{ value.heading }}</h2>
<div>
{{ value.paragraph|richtext }}
</div>
</div>

View File

@ -1,15 +0,0 @@
{% extends "base.html" %}
{% load wagtailcore_tags %}
{% block body_class %}template-homepage{% endblock %}
{% block content %}
<h1>{{ page.title }}</h1>
{% for block in page.body %}
<section class="section-{{ block.block_type }}">
<p><em>Section for {{ block.block_type }}.</em></p>
{% include_block block %}
</section>
<hr>
{% endfor %}
{% endblock %}

View File

@ -1,85 +1,25 @@
from __future__ import absolute_import, unicode_literals
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from wagtail.core import blocks
from wagtail.images.blocks import ImageChooserBlock
from wagtail_personalisation.adapters import get_segment_adapter
from wagtail_personalisation.models import Segment
def list_segment_choices():
"""Get a list of segment choices visible in the admin site when editing
BasePersonalisedStructBlock and its derived classes."""
yield (-1, _('Visible to everyone'))
for pk, name in Segment.objects.values_list('pk', 'name'):
yield pk, name
class BasePersonalisedStructBlock(blocks.StructBlock):
"""Base class for personalised struct blocks."""
class PersonalisedStructBlock(blocks.StructBlock):
"""Struct block that allows personalisation per block."""
segment = blocks.ChoiceBlock(
choices=list_segment_choices,
required=False, label=_("Personalisation segment"),
help_text=_("Only show this content block for users in this segment"))
def __init__(self, *args, **kwargs):
"""Instantiate personalised struct block.
The arguments are the same as for the blocks.StructBlock constructor and
one addtional one.
Keyword Arguments:
render_fields: List with field names to be rendered or None to use
the default block rendering. Please set to None if using block
with template since then it's the template that takes care
of what fields are rendered.
"""
render_fields = kwargs.pop('render_fields',
self._meta_class.render_fields)
super(BasePersonalisedStructBlock, self).__init__(*args, **kwargs)
# Check "render_fields" are either a list or None.
if isinstance(render_fields, tuple):
render_fields = list(render_fields)
if render_fields is not None \
and not isinstance(render_fields, list):
raise ValueError('"render_fields" has to be a list or None.')
elif isinstance(render_fields, list) \
and not set(render_fields).issubset(self.child_blocks):
raise ValueError('"render_fields" has to contain name(s) of the '
'specified blocks.')
else:
setattr(self.meta, 'render_fields', render_fields)
# Template can be used only when "render_fields" is set to None.
if self.meta.render_fields is not None \
and getattr(self.meta, 'template', None):
raise ValueError('"render_fields" has to be set to None when using '
'template.')
def is_visible(self, value, request):
"""Check whether user should see the block based on their segments.
:param value: The value from the block.
:type value: dict
:returns: True if user should see the block.
:rtype: bool
"""
if int(value['segment']) == -1:
return True
if value['segment']:
for segment in get_segment_adapter(request).get_segments():
if segment.id == int(value['segment']):
return True
return False
def render(self, value, context=None):
"""Only render this content block for users in this segment.
@ -91,80 +31,14 @@ class BasePersonalisedStructBlock(blocks.StructBlock):
:rtype: blocks.StructBlock or empty str
"""
if not self.is_visible(value, context['request']):
return ""
request = context['request']
adapter = get_segment_adapter(request)
user_segments = adapter.get_segments()
if self.meta.render_fields is None:
return super(BasePersonalisedStructBlock, self).render(
value, context)
if value['segment']:
for segment in user_segments:
if segment.id == int(value['segment']):
return super(PersonalisedStructBlock, self).render(
value, context)
if isinstance(self.meta.render_fields, list):
render_value = ''
for field_name in self.meta.render_fields:
if hasattr(value.bound_blocks[field_name], 'render_as_block'):
block_value = value.bound_blocks[field_name].render_as_block(context=context)
else:
block_value = force_text(value[field_name])
if block_value != 'None':
render_value += block_value
return render_value
raise RuntimeError('"render_fields" is neither "None" or "list" '
'during rendering.')
class Meta:
"""
Setting render field will define which field gets rendered.
Please use a name of the field. If none, it will render the whole block.
"""
render_fields = None
class PersonalisedStructBlock(BasePersonalisedStructBlock):
"""Struct block that allows personalisation per block."""
class Meta:
label = _('Personalised Block')
render_fields = None
class PersonalisedRichTextBlock(BasePersonalisedStructBlock):
"""Rich text block that allows personalisation."""
rich_text = blocks.RichTextBlock(label=_('Rich Text'))
class Meta:
icon = blocks.RichTextBlock._meta_class.icon
label = _('Personalised Rich Text')
render_fields = ['rich_text']
class PersonalisedTextBlock(BasePersonalisedStructBlock):
"""Text block that allows personalisation."""
text = blocks.TextBlock(label=_('Mutli-line Text'))
class Meta:
icon = blocks.TextBlock._meta_class.icon
label = _('Personalised Multi-line Text')
render_fields = ['text']
class PersonalisedCharBlock(BasePersonalisedStructBlock):
"""Char block that allows personalisation."""
char = blocks.CharBlock(label=_('Text'))
class Meta:
icon = blocks.CharBlock._meta_class.icon
label = _('Personalised Single-line Text')
render_fields = ['char']
class PersonalisedImageChooserBlock(BasePersonalisedStructBlock):
"""Image chooser block that allows personalisation."""
image = ImageChooserBlock(label=_('Image'))
class Meta:
icon = ImageChooserBlock._meta_class.icon
label = _('Personalised Image')
render_fields = ['image']
return ""

View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-08-10 13:48
from __future__ import unicode_literals
from django.db import migrations, models
def forward(apps, schema_editor):
Segment = apps.get_model('wagtail_personalisation', 'Segment')
for segment in Segment.objects.all():
segment.enabled = segment.status == 'enabled'
segment.save()
def backward(apps, schema_editor):
Segment = apps.get_model('wagtail_personalisation', 'Segment')
for segment in Segment.objects.all():
if segment.enabled:
segment.status = 'enabled'
else:
segment.status = 'disabled'
segment.save()
class Migration(migrations.Migration):
dependencies = [
('wagtail_personalisation', '0023_personalisablepagemetadata_variant_cascade'),
]
operations = [
migrations.AddField(
model_name='segment',
name='enabled',
field=models.BooleanField(default=True, help_text='Should the segment be active?'),
),
migrations.RunPython(forward, reverse_code=backward),
migrations.RemoveField(
model_name='segment',
name='status',
),
]

View File

@ -22,20 +22,12 @@ from .forms import SegmentAdminForm
class SegmentQuerySet(models.QuerySet):
def enabled(self):
return self.filter(status=self.model.STATUS_ENABLED)
return self.filter(enabled=True)
@python_2_unicode_compatible
class Segment(ClusterableModel):
"""The segment model."""
STATUS_ENABLED = 'enabled'
STATUS_DISABLED = 'disabled'
STATUS_CHOICES = (
(STATUS_ENABLED, _('Enabled')),
(STATUS_DISABLED, _('Disabled')),
)
TYPE_DYNAMIC = 'dynamic'
TYPE_STATIC = 'static'
@ -50,8 +42,8 @@ class Segment(ClusterableModel):
enable_date = models.DateTimeField(null=True, editable=False)
disable_date = models.DateTimeField(null=True, editable=False)
visit_count = models.PositiveIntegerField(default=0, editable=False)
status = models.CharField(
max_length=20, choices=STATUS_CHOICES, default=STATUS_ENABLED)
enabled = models.BooleanField(
default=True, help_text=_("Should the segment be active?"))
persistent = models.BooleanField(
default=False, help_text=_("Should the segment persist between visits?"))
match_any = models.BooleanField(
@ -111,7 +103,7 @@ class Segment(ClusterableModel):
MultiFieldPanel([
FieldPanel('name', classname="title"),
FieldRowPanel([
FieldPanel('status'),
FieldPanel('enabled'),
FieldPanel('persistent'),
]),
FieldPanel('match_any'),
@ -178,9 +170,7 @@ class Segment(ClusterableModel):
return segment_rules
def toggle(self, save=True):
self.status = (
self.STATUS_ENABLED if self.status == self.STATUS_DISABLED
else self.STATUS_DISABLED)
self.enabled = not self.enabled
if save:
self.save()

View File

@ -7,16 +7,16 @@ from wagtail_personalisation.models import Segment
def check_status_change(sender, instance, *args, **kwargs):
"""Check if the status has changed. Alter dates accordingly."""
try:
original_status = sender.objects.get(pk=instance.id).status
original_status = sender.objects.get(pk=instance.id).enabled
except sender.DoesNotExist:
original_status = ""
original_status = None
if original_status != instance.status:
if instance.status == instance.STATUS_ENABLED:
if original_status != instance.enabled:
if instance.enabled is True:
instance.enable_date = timezone.now()
instance.visit_count = 0
return instance
if instance.status == instance.STATUS_DISABLED:
if instance.enabled is False:
instance.disable_date = timezone.now()

View File

@ -22,7 +22,7 @@
<div class="nice-padding block_container">
{% if all_count %}
{% for segment in object_list %}
<div class="block block--{{ segment.status }}" onclick="location.href = '{% url 'wagtail_personalisation_segment_modeladmin_edit' segment.pk %}'">
<div class="block block--{{ segment.enabled|yesno:"enabled,disabled" }}" onclick="location.href = '{% url 'wagtail_personalisation_segment_modeladmin_edit' segment.pk %}'">
<h2>{{ segment }}</h2>
<div class="inspect_container">
@ -98,10 +98,10 @@
{% if user_can_create %}
<ul class="block_actions">
{% if segment.status == segment.STATUS_DISABLED %}
<li><a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Enable this segment" %}">enable</a></li>
{% elif segment.status == segment.STATUS_ENABLED %}
{% if segment.enabled %}
<li><a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Disable this segment" %}">disable</a></li>
{% else %}
<li><a href="{% url 'segment:toggle' segment.pk %}" title="{% trans "Enable this segment" %}">enable</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 %}

View File

@ -83,7 +83,7 @@ class SegmentModelAdmin(ModelAdmin):
delete_view_class = SegmentModelDeleteView
menu_icon = 'fa-snowflake-o'
add_to_settings_menu = False
list_display = ('name', 'persistent', 'match_any', 'status',
list_display = ('name', 'persistent', 'match_any', 'enabled',
'page_count', 'variant_count', 'statistics')
index_view_extra_js = ['js/commons.js', 'js/index.js']
index_view_extra_css = ['css/index.css']

View File

@ -7,7 +7,7 @@ from wagtail_personalisation import models
class SegmentFactory(factory.DjangoModelFactory):
name = 'TestSegment'
status = models.Segment.STATUS_ENABLED
enabled = True
class Meta:
model = models.Segment

View File

@ -64,7 +64,7 @@ def test_refresh_removes_disabled(rf):
adapter.set_segments([segment_1, segment_2])
adapter = adapters.SessionSegmentsAdapter(request)
segment_1.status = segment_1.STATUS_DISABLED
segment_1.enabled = False
segment_1.save()
adapter.refresh()

View File

@ -15,14 +15,14 @@ from wagtail_personalisation.rules import TimeRule
@pytest.mark.django_db
def test_segment_create():
factoried_segment = SegmentFactory()
segment = Segment(name='TestSegment', status='enabled')
segment = Segment(name='TestSegment', enabled=True)
TimeRule(
start_time=datetime.time(8, 0, 0),
end_time=datetime.time(23, 0, 0),
segment=segment)
assert factoried_segment.name == segment.name
assert factoried_segment.status == segment.status
assert factoried_segment.enabled == segment.enabled
@pytest.mark.django_db

View File

@ -12,7 +12,7 @@ from wagtail_personalisation.rules import TimeRule, VisitCountRule
def form_with_data(segment, *rules):
model_fields = ['type', 'status', 'count', 'name', 'match_any', 'randomisation_percent']
model_fields = ['type', 'enabled', 'count', 'name', 'match_any', 'randomisation_percent']
class TestSegmentAdminForm(SegmentAdminForm):
class Meta: